mirror of
https://github.com/hashicorp/vault.git
synced 2025-12-16 15:01:13 +01:00
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
This commit is contained in:
parent
3e97f5cf62
commit
012c165b02
139
command/agent.go
139
command/agent.go
@ -29,10 +29,12 @@ import (
|
|||||||
"github.com/hashicorp/vault/command/agent/auth/jwt"
|
"github.com/hashicorp/vault/command/agent/auth/jwt"
|
||||||
"github.com/hashicorp/vault/command/agent/auth/kubernetes"
|
"github.com/hashicorp/vault/command/agent/auth/kubernetes"
|
||||||
"github.com/hashicorp/vault/command/agent/cache"
|
"github.com/hashicorp/vault/command/agent/cache"
|
||||||
|
"github.com/hashicorp/vault/command/agent/config"
|
||||||
agentConfig "github.com/hashicorp/vault/command/agent/config"
|
agentConfig "github.com/hashicorp/vault/command/agent/config"
|
||||||
"github.com/hashicorp/vault/command/agent/sink"
|
"github.com/hashicorp/vault/command/agent/sink"
|
||||||
"github.com/hashicorp/vault/command/agent/sink/file"
|
"github.com/hashicorp/vault/command/agent/sink/file"
|
||||||
"github.com/hashicorp/vault/command/agent/sink/inmem"
|
"github.com/hashicorp/vault/command/agent/sink/inmem"
|
||||||
|
"github.com/hashicorp/vault/command/agent/template"
|
||||||
gatedwriter "github.com/hashicorp/vault/helper/gated-writer"
|
gatedwriter "github.com/hashicorp/vault/helper/gated-writer"
|
||||||
"github.com/hashicorp/vault/sdk/helper/consts"
|
"github.com/hashicorp/vault/sdk/helper/consts"
|
||||||
"github.com/hashicorp/vault/sdk/helper/logging"
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||||
@ -215,44 +217,48 @@ func (c *AgentCommand) Run(args []string) int {
|
|||||||
c.UI.Info("No auto_auth block found in config file, not starting automatic authentication feature")
|
c.UI.Info("No auto_auth block found in config file, not starting automatic authentication feature")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.Vault != nil {
|
// create an empty Vault configuration if none was loaded from file. The
|
||||||
c.setStringFlag(f, config.Vault.Address, &StringVar{
|
// follow-up setStringFlag calls will populate with defaults if otherwise
|
||||||
Name: flagNameAddress,
|
// omitted
|
||||||
Target: &c.flagAddress,
|
if config.Vault == nil {
|
||||||
Default: "https://127.0.0.1:8200",
|
config.Vault = new(agentConfig.Vault)
|
||||||
EnvVar: api.EnvVaultAddress,
|
|
||||||
})
|
|
||||||
c.setStringFlag(f, config.Vault.CACert, &StringVar{
|
|
||||||
Name: flagNameCACert,
|
|
||||||
Target: &c.flagCACert,
|
|
||||||
Default: "",
|
|
||||||
EnvVar: api.EnvVaultCACert,
|
|
||||||
})
|
|
||||||
c.setStringFlag(f, config.Vault.CAPath, &StringVar{
|
|
||||||
Name: flagNameCAPath,
|
|
||||||
Target: &c.flagCAPath,
|
|
||||||
Default: "",
|
|
||||||
EnvVar: api.EnvVaultCAPath,
|
|
||||||
})
|
|
||||||
c.setStringFlag(f, config.Vault.ClientCert, &StringVar{
|
|
||||||
Name: flagNameClientCert,
|
|
||||||
Target: &c.flagClientCert,
|
|
||||||
Default: "",
|
|
||||||
EnvVar: api.EnvVaultClientCert,
|
|
||||||
})
|
|
||||||
c.setStringFlag(f, config.Vault.ClientKey, &StringVar{
|
|
||||||
Name: flagNameClientKey,
|
|
||||||
Target: &c.flagClientKey,
|
|
||||||
Default: "",
|
|
||||||
EnvVar: api.EnvVaultClientKey,
|
|
||||||
})
|
|
||||||
c.setBoolFlag(f, config.Vault.TLSSkipVerify, &BoolVar{
|
|
||||||
Name: flagNameTLSSkipVerify,
|
|
||||||
Target: &c.flagTLSSkipVerify,
|
|
||||||
Default: false,
|
|
||||||
EnvVar: api.EnvVaultSkipVerify,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
c.setStringFlag(f, config.Vault.Address, &StringVar{
|
||||||
|
Name: flagNameAddress,
|
||||||
|
Target: &c.flagAddress,
|
||||||
|
Default: "https://127.0.0.1:8200",
|
||||||
|
EnvVar: api.EnvVaultAddress,
|
||||||
|
})
|
||||||
|
c.setStringFlag(f, config.Vault.CACert, &StringVar{
|
||||||
|
Name: flagNameCACert,
|
||||||
|
Target: &c.flagCACert,
|
||||||
|
Default: "",
|
||||||
|
EnvVar: api.EnvVaultCACert,
|
||||||
|
})
|
||||||
|
c.setStringFlag(f, config.Vault.CAPath, &StringVar{
|
||||||
|
Name: flagNameCAPath,
|
||||||
|
Target: &c.flagCAPath,
|
||||||
|
Default: "",
|
||||||
|
EnvVar: api.EnvVaultCAPath,
|
||||||
|
})
|
||||||
|
c.setStringFlag(f, config.Vault.ClientCert, &StringVar{
|
||||||
|
Name: flagNameClientCert,
|
||||||
|
Target: &c.flagClientCert,
|
||||||
|
Default: "",
|
||||||
|
EnvVar: api.EnvVaultClientCert,
|
||||||
|
})
|
||||||
|
c.setStringFlag(f, config.Vault.ClientKey, &StringVar{
|
||||||
|
Name: flagNameClientKey,
|
||||||
|
Target: &c.flagClientKey,
|
||||||
|
Default: "",
|
||||||
|
EnvVar: api.EnvVaultClientKey,
|
||||||
|
})
|
||||||
|
c.setBoolFlag(f, config.Vault.TLSSkipVerify, &BoolVar{
|
||||||
|
Name: flagNameTLSSkipVerify,
|
||||||
|
Target: &c.flagTLSSkipVerify,
|
||||||
|
Default: false,
|
||||||
|
EnvVar: api.EnvVaultSkipVerify,
|
||||||
|
})
|
||||||
|
|
||||||
infoKeys := make([]string, 0, 10)
|
infoKeys := make([]string, 0, 10)
|
||||||
info := make(map[string]string)
|
info := make(map[string]string)
|
||||||
@ -294,10 +300,14 @@ func (c *AgentCommand) Run(args []string) int {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ctx and cancelFunc are passed to the AuthHandler, SinkServer, and
|
||||||
|
// TemplateServer that periodically listen for ctx.Done() to fire and shut
|
||||||
|
// down accordingly.
|
||||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
|
||||||
var method auth.AuthMethod
|
var method auth.AuthMethod
|
||||||
var sinks []*sink.SinkConfig
|
var sinks []*sink.SinkConfig
|
||||||
|
var namespace string
|
||||||
if config.AutoAuth != nil {
|
if config.AutoAuth != nil {
|
||||||
for _, sc := range config.AutoAuth.Sinks {
|
for _, sc := range config.AutoAuth.Sinks {
|
||||||
switch sc.Type {
|
switch sc.Type {
|
||||||
@ -327,7 +337,8 @@ func (c *AgentCommand) Run(args []string) int {
|
|||||||
// Check if a default namespace has been set
|
// Check if a default namespace has been set
|
||||||
mountPath := config.AutoAuth.Method.MountPath
|
mountPath := config.AutoAuth.Method.MountPath
|
||||||
if config.AutoAuth.Method.Namespace != "" {
|
if config.AutoAuth.Method.Namespace != "" {
|
||||||
mountPath = path.Join(config.AutoAuth.Method.Namespace, mountPath)
|
namespace = config.AutoAuth.Method.Namespace
|
||||||
|
mountPath = path.Join(namespace, mountPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
authConfig := &auth.AuthConfig{
|
authConfig := &auth.AuthConfig{
|
||||||
@ -486,14 +497,21 @@ func (c *AgentCommand) Run(args []string) int {
|
|||||||
defer c.cleanupGuard.Do(listenerCloseFunc)
|
defer c.cleanupGuard.Do(listenerCloseFunc)
|
||||||
}
|
}
|
||||||
|
|
||||||
var ssDoneCh, ahDoneCh chan struct{}
|
// Listen for signals
|
||||||
|
// TODO: implement support for SIGHUP reloading of configuration
|
||||||
|
// signal.Notify(c.signalCh)
|
||||||
|
|
||||||
|
var ssDoneCh, ahDoneCh, tsDoneCh, unblockCh chan struct{}
|
||||||
|
var ts *template.Server
|
||||||
// Start auto-auth and sink servers
|
// Start auto-auth and sink servers
|
||||||
if method != nil {
|
if method != nil {
|
||||||
|
enableTokenCh := len(config.Templates) > 0
|
||||||
ah := auth.NewAuthHandler(&auth.AuthHandlerConfig{
|
ah := auth.NewAuthHandler(&auth.AuthHandlerConfig{
|
||||||
Logger: c.logger.Named("auth.handler"),
|
Logger: c.logger.Named("auth.handler"),
|
||||||
Client: c.client,
|
Client: c.client,
|
||||||
WrapTTL: config.AutoAuth.Method.WrapTTL,
|
WrapTTL: config.AutoAuth.Method.WrapTTL,
|
||||||
EnableReauthOnNewCredentials: config.AutoAuth.EnableReauthOnNewCredentials,
|
EnableReauthOnNewCredentials: config.AutoAuth.EnableReauthOnNewCredentials,
|
||||||
|
EnableTemplateTokenCh: enableTokenCh,
|
||||||
})
|
})
|
||||||
ahDoneCh = ah.DoneCh
|
ahDoneCh = ah.DoneCh
|
||||||
|
|
||||||
@ -504,8 +522,20 @@ func (c *AgentCommand) Run(args []string) int {
|
|||||||
})
|
})
|
||||||
ssDoneCh = ss.DoneCh
|
ssDoneCh = ss.DoneCh
|
||||||
|
|
||||||
|
// create an independent vault configuration for Consul Template to use
|
||||||
|
vaultConfig := c.setupTemplateConfig()
|
||||||
|
ts = template.NewServer(&template.ServerConfig{
|
||||||
|
Logger: c.logger.Named("template.server"),
|
||||||
|
VaultConf: vaultConfig,
|
||||||
|
Namespace: namespace,
|
||||||
|
ExitAfterAuth: config.ExitAfterAuth,
|
||||||
|
})
|
||||||
|
tsDoneCh = ts.DoneCh
|
||||||
|
unblockCh = ts.UnblockCh
|
||||||
|
|
||||||
go ah.Run(ctx, method)
|
go ah.Run(ctx, method)
|
||||||
go ss.Run(ctx, ah.OutputCh, sinks)
|
go ss.Run(ctx, ah.OutputCh, sinks)
|
||||||
|
go ts.Run(ctx, ah.TemplateTokenCh, config.Templates)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Server configuration output
|
// Server configuration output
|
||||||
@ -536,6 +566,15 @@ func (c *AgentCommand) Run(args []string) int {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// If the template server is running and we've assinged the Unblock channel,
|
||||||
|
// wait for the template to render
|
||||||
|
if unblockCh != nil {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-ts.UnblockCh:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-ssDoneCh:
|
case <-ssDoneCh:
|
||||||
// This will happen if we exit-on-auth
|
// This will happen if we exit-on-auth
|
||||||
@ -549,6 +588,10 @@ func (c *AgentCommand) Run(args []string) int {
|
|||||||
if ssDoneCh != nil {
|
if ssDoneCh != nil {
|
||||||
<-ssDoneCh
|
<-ssDoneCh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tsDoneCh != nil {
|
||||||
|
<-tsDoneCh
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
@ -648,3 +691,21 @@ func (c *AgentCommand) removePidFile(pidPath string) error {
|
|||||||
}
|
}
|
||||||
return os.Remove(pidPath)
|
return os.Remove(pidPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// setupTemplateConfig creates a config.Vault struct for use by Consul Template.
|
||||||
|
// Consul Template does not currently allow us to pass in a configured API
|
||||||
|
// client, unlike the AuthHandler and SinkServer that reuse the client created
|
||||||
|
// in this Run() method. Here we build a config.Vault struct for use by the
|
||||||
|
// Template Server that matches the configuration used to create the client
|
||||||
|
// (c.client), but in a struct of type config.Vault so that Consul Template can
|
||||||
|
// create it's own api client internally.
|
||||||
|
func (c *AgentCommand) setupTemplateConfig() *config.Vault {
|
||||||
|
return &config.Vault{
|
||||||
|
Address: c.flagAddress,
|
||||||
|
CACert: c.flagCACert,
|
||||||
|
CAPath: c.flagCAPath,
|
||||||
|
ClientCert: c.flagClientCert,
|
||||||
|
ClientKey: c.flagClientKey,
|
||||||
|
TLSSkipVerify: c.flagTLSSkipVerify,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
15
command/agent/README.md
Normal file
15
command/agent/README.md
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Vault Agent
|
||||||
|
|
||||||
|
Vault Agent is a client daemon that provides Auth-Auth, Caching, and Template
|
||||||
|
features.
|
||||||
|
|
||||||
|
Vault Agent provides a number of different helper features, specifically
|
||||||
|
addressing the following challenges:
|
||||||
|
|
||||||
|
- Automatic authentication
|
||||||
|
- Secure delivery/storage of tokens
|
||||||
|
- Lifecycle management of these tokens (renewal & re-authentication)
|
||||||
|
|
||||||
|
See the usage documentation on the Vault website here:
|
||||||
|
|
||||||
|
- https://www.vaultproject.io/docs/agent/
|
||||||
@ -30,11 +30,13 @@ type AuthConfig struct {
|
|||||||
type AuthHandler struct {
|
type AuthHandler struct {
|
||||||
DoneCh chan struct{}
|
DoneCh chan struct{}
|
||||||
OutputCh chan string
|
OutputCh chan string
|
||||||
|
TemplateTokenCh chan string
|
||||||
logger hclog.Logger
|
logger hclog.Logger
|
||||||
client *api.Client
|
client *api.Client
|
||||||
random *rand.Rand
|
random *rand.Rand
|
||||||
wrapTTL time.Duration
|
wrapTTL time.Duration
|
||||||
enableReauthOnNewCredentials bool
|
enableReauthOnNewCredentials bool
|
||||||
|
enableTemplateTokenCh bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthHandlerConfig struct {
|
type AuthHandlerConfig struct {
|
||||||
@ -42,6 +44,7 @@ type AuthHandlerConfig struct {
|
|||||||
Client *api.Client
|
Client *api.Client
|
||||||
WrapTTL time.Duration
|
WrapTTL time.Duration
|
||||||
EnableReauthOnNewCredentials bool
|
EnableReauthOnNewCredentials bool
|
||||||
|
EnableTemplateTokenCh bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthHandler(conf *AuthHandlerConfig) *AuthHandler {
|
func NewAuthHandler(conf *AuthHandlerConfig) *AuthHandler {
|
||||||
@ -50,11 +53,13 @@ func NewAuthHandler(conf *AuthHandlerConfig) *AuthHandler {
|
|||||||
// This is buffered so that if we try to output after the sink server
|
// This is buffered so that if we try to output after the sink server
|
||||||
// has been shut down, during agent shutdown, we won't block
|
// has been shut down, during agent shutdown, we won't block
|
||||||
OutputCh: make(chan string, 1),
|
OutputCh: make(chan string, 1),
|
||||||
|
TemplateTokenCh: make(chan string, 1),
|
||||||
logger: conf.Logger,
|
logger: conf.Logger,
|
||||||
client: conf.Client,
|
client: conf.Client,
|
||||||
random: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
|
random: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
|
||||||
wrapTTL: conf.WrapTTL,
|
wrapTTL: conf.WrapTTL,
|
||||||
enableReauthOnNewCredentials: conf.EnableReauthOnNewCredentials,
|
enableReauthOnNewCredentials: conf.EnableReauthOnNewCredentials,
|
||||||
|
enableTemplateTokenCh: conf.EnableTemplateTokenCh,
|
||||||
}
|
}
|
||||||
|
|
||||||
return ah
|
return ah
|
||||||
@ -77,6 +82,7 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) {
|
|||||||
am.Shutdown()
|
am.Shutdown()
|
||||||
close(ah.OutputCh)
|
close(ah.OutputCh)
|
||||||
close(ah.DoneCh)
|
close(ah.DoneCh)
|
||||||
|
close(ah.TemplateTokenCh)
|
||||||
ah.logger.Info("auth handler stopped")
|
ah.logger.Info("auth handler stopped")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@ -163,6 +169,9 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) {
|
|||||||
}
|
}
|
||||||
ah.logger.Info("authentication successful, sending wrapped token to sinks and pausing")
|
ah.logger.Info("authentication successful, sending wrapped token to sinks and pausing")
|
||||||
ah.OutputCh <- string(wrappedResp)
|
ah.OutputCh <- string(wrappedResp)
|
||||||
|
if ah.enableTemplateTokenCh {
|
||||||
|
ah.TemplateTokenCh <- string(wrappedResp)
|
||||||
|
}
|
||||||
|
|
||||||
am.CredSuccess()
|
am.CredSuccess()
|
||||||
|
|
||||||
@ -189,6 +198,9 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) {
|
|||||||
}
|
}
|
||||||
ah.logger.Info("authentication successful, sending token to sinks")
|
ah.logger.Info("authentication successful, sending token to sinks")
|
||||||
ah.OutputCh <- secret.Auth.ClientToken
|
ah.OutputCh <- secret.Auth.ClientToken
|
||||||
|
if ah.enableTemplateTokenCh {
|
||||||
|
ah.TemplateTokenCh <- secret.Auth.ClientToken
|
||||||
|
}
|
||||||
|
|
||||||
am.CredSuccess()
|
am.CredSuccess()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,7 +87,8 @@ consumption:
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ah.OutputCh:
|
case <-ah.OutputCh:
|
||||||
// Nothing
|
case <-ah.TemplateTokenCh:
|
||||||
|
// Nothing
|
||||||
case <-time.After(stopTime.Sub(time.Now())):
|
case <-time.After(stopTime.Sub(time.Now())):
|
||||||
if !closed {
|
if !closed {
|
||||||
cancelFunc()
|
cancelFunc()
|
||||||
|
|||||||
@ -8,24 +8,28 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
ctconfig "github.com/hashicorp/consul-template/config"
|
||||||
"github.com/hashicorp/errwrap"
|
"github.com/hashicorp/errwrap"
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
"github.com/hashicorp/hcl/hcl/ast"
|
"github.com/hashicorp/hcl/hcl/ast"
|
||||||
"github.com/hashicorp/vault/helper/namespace"
|
"github.com/hashicorp/vault/helper/namespace"
|
||||||
"github.com/hashicorp/vault/sdk/helper/parseutil"
|
"github.com/hashicorp/vault/sdk/helper/parseutil"
|
||||||
|
"github.com/mitchellh/mapstructure"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is the configuration for the vault server.
|
// Config is the configuration for the vault server.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
AutoAuth *AutoAuth `hcl:"auto_auth"`
|
AutoAuth *AutoAuth `hcl:"auto_auth"`
|
||||||
ExitAfterAuth bool `hcl:"exit_after_auth"`
|
ExitAfterAuth bool `hcl:"exit_after_auth"`
|
||||||
PidFile string `hcl:"pid_file"`
|
PidFile string `hcl:"pid_file"`
|
||||||
Listeners []*Listener `hcl:"listeners"`
|
Listeners []*Listener `hcl:"listeners"`
|
||||||
Cache *Cache `hcl:"cache"`
|
Cache *Cache `hcl:"cache"`
|
||||||
Vault *Vault `hcl:"vault"`
|
Vault *Vault `hcl:"vault"`
|
||||||
|
Templates []*ctconfig.TemplateConfig `hcl:"templates"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vault contains configuration for connnecting to Vault servers
|
||||||
type Vault struct {
|
type Vault struct {
|
||||||
Address string `hcl:"address"`
|
Address string `hcl:"address"`
|
||||||
CACert string `hcl:"ca_cert"`
|
CACert string `hcl:"ca_cert"`
|
||||||
@ -36,10 +40,12 @@ type Vault struct {
|
|||||||
ClientKey string `hcl:"client_key"`
|
ClientKey string `hcl:"client_key"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache contains any configuration needed for Cache mode
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
UseAutoAuthToken bool `hcl:"use_auto_auth_token"`
|
UseAutoAuthToken bool `hcl:"use_auto_auth_token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listener contains configuration for any Vault Agent listeners
|
||||||
type Listener struct {
|
type Listener struct {
|
||||||
Type string
|
Type string
|
||||||
Config map[string]interface{}
|
Config map[string]interface{}
|
||||||
@ -48,6 +54,7 @@ type Listener struct {
|
|||||||
// RequireRequestHeader is a listener configuration option
|
// RequireRequestHeader is a listener configuration option
|
||||||
const RequireRequestHeader = "require_request_header"
|
const RequireRequestHeader = "require_request_header"
|
||||||
|
|
||||||
|
// AutoAuth is the configured authentication method and sinks
|
||||||
type AutoAuth struct {
|
type AutoAuth struct {
|
||||||
Method *Method `hcl:"-"`
|
Method *Method `hcl:"-"`
|
||||||
Sinks []*Sink `hcl:"sinks"`
|
Sinks []*Sink `hcl:"sinks"`
|
||||||
@ -57,6 +64,7 @@ type AutoAuth struct {
|
|||||||
EnableReauthOnNewCredentials bool `hcl:"enable_reauth_on_new_credentials"`
|
EnableReauthOnNewCredentials bool `hcl:"enable_reauth_on_new_credentials"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Method represents the configuration for the authentication backend
|
||||||
type Method struct {
|
type Method struct {
|
||||||
Type string
|
Type string
|
||||||
MountPath string `hcl:"mount_path"`
|
MountPath string `hcl:"mount_path"`
|
||||||
@ -66,6 +74,7 @@ type Method struct {
|
|||||||
Config map[string]interface{}
|
Config map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sink defines a location to write the authenticated token
|
||||||
type Sink struct {
|
type Sink struct {
|
||||||
Type string
|
Type string
|
||||||
WrapTTLRaw interface{} `hcl:"wrap_ttl"`
|
WrapTTLRaw interface{} `hcl:"wrap_ttl"`
|
||||||
@ -116,16 +125,18 @@ func LoadConfig(path string) (*Config, error) {
|
|||||||
return nil, errwrap.Wrapf("error parsing 'auto_auth': {{err}}", err)
|
return nil, errwrap.Wrapf("error parsing 'auto_auth': {{err}}", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseListeners(&result, list)
|
if err := parseListeners(&result, list); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, errwrap.Wrapf("error parsing 'listeners': {{err}}", err)
|
return nil, errwrap.Wrapf("error parsing 'listeners': {{err}}", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = parseCache(&result, list)
|
if err := parseCache(&result, list); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, errwrap.Wrapf("error parsing 'cache':{{err}}", err)
|
return nil, errwrap.Wrapf("error parsing 'cache':{{err}}", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := parseTemplates(&result, list); err != nil {
|
||||||
|
return nil, errwrap.Wrapf("error parsing 'template': {{err}}", err)
|
||||||
|
}
|
||||||
|
|
||||||
if result.Cache != nil {
|
if result.Cache != nil {
|
||||||
if len(result.Listeners) < 1 {
|
if len(result.Listeners) < 1 {
|
||||||
return nil, fmt.Errorf("at least one listener required when cache enabled")
|
return nil, fmt.Errorf("at least one listener required when cache enabled")
|
||||||
@ -411,3 +422,65 @@ func parseSinks(result *Config, list *ast.ObjectList) error {
|
|||||||
result.AutoAuth.Sinks = ts
|
result.AutoAuth.Sinks = ts
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseTemplates(result *Config, list *ast.ObjectList) error {
|
||||||
|
name := "template"
|
||||||
|
|
||||||
|
templateList := list.Filter(name)
|
||||||
|
if len(templateList.Items) < 1 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var tcs []*ctconfig.TemplateConfig
|
||||||
|
|
||||||
|
for _, item := range templateList.Items {
|
||||||
|
var shadow interface{}
|
||||||
|
if err := hcl.DecodeObject(&shadow, item.Val); err != nil {
|
||||||
|
return fmt.Errorf("error decoding config: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to a map and flatten the keys we want to flatten
|
||||||
|
parsed, ok := shadow.(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return errors.New("error converting config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// flatten the wait field. The initial "wait" value, if given, is a
|
||||||
|
// []map[string]interface{}, but we need it to be map[string]interface{}.
|
||||||
|
// Consul Template has a method flattenKeys that walks all of parsed and
|
||||||
|
// flattens every key. For Vault Agent, we only care about the wait input.
|
||||||
|
// Only one wait stanza is supported, however Consul Template does not error
|
||||||
|
// with multiple instead it flattens them down, with last value winning.
|
||||||
|
// Here we take the last element of the parsed["wait"] slice to keep
|
||||||
|
// consistency with Consul Template behavior.
|
||||||
|
wait, ok := parsed["wait"].([]map[string]interface{})
|
||||||
|
if ok {
|
||||||
|
parsed["wait"] = wait[len(wait)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
var tc ctconfig.TemplateConfig
|
||||||
|
|
||||||
|
// Use mapstructure to populate the basic config fields
|
||||||
|
var md mapstructure.Metadata
|
||||||
|
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
|
DecodeHook: mapstructure.ComposeDecodeHookFunc(
|
||||||
|
ctconfig.StringToFileModeFunc(),
|
||||||
|
ctconfig.StringToWaitDurationHookFunc(),
|
||||||
|
mapstructure.StringToSliceHookFunc(","),
|
||||||
|
mapstructure.StringToTimeDurationHookFunc(),
|
||||||
|
),
|
||||||
|
ErrorUnused: true,
|
||||||
|
Metadata: &md,
|
||||||
|
Result: &tc,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("mapstructure decoder creation failed")
|
||||||
|
}
|
||||||
|
if err := decoder.Decode(parsed); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tcs = append(tcs, &tc)
|
||||||
|
}
|
||||||
|
result.Templates = tcs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-test/deep"
|
"github.com/go-test/deep"
|
||||||
|
ctconfig "github.com/hashicorp/consul-template/config"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/pointerutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLoadConfigFile_AgentCache(t *testing.T) {
|
func TestLoadConfigFile_AgentCache(t *testing.T) {
|
||||||
@ -93,8 +95,14 @@ func TestLoadConfigFile_AgentCache(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestLoadConfigFile(t *testing.T) {
|
func TestLoadConfigFile(t *testing.T) {
|
||||||
os.Setenv("TEST_AAD_ENV", "aad")
|
if err := os.Setenv("TEST_AAD_ENV", "aad"); err != nil {
|
||||||
defer os.Unsetenv("TEST_AAD_ENV")
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := os.Unsetenv("TEST_AAD_ENV"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
config, err := LoadConfig("./test-fixtures/config.hcl")
|
config, err := LoadConfig("./test-fixtures/config.hcl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -278,3 +286,106 @@ func TestLoadConfigFile_AgentCache_AutoAuth_NoSink(t *testing.T) {
|
|||||||
t.Fatal(diff)
|
t.Fatal(diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLoadConfigFile_Template tests template definitions in Vault Agent
|
||||||
|
func TestLoadConfigFile_Template(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
fixturePath string
|
||||||
|
expectedTemplates []*ctconfig.TemplateConfig
|
||||||
|
}{
|
||||||
|
"min": {
|
||||||
|
fixturePath: "./test-fixtures/config-template-min.hcl",
|
||||||
|
expectedTemplates: []*ctconfig.TemplateConfig{
|
||||||
|
&ctconfig.TemplateConfig{
|
||||||
|
Source: pointerutil.StringPtr("/path/on/disk/to/template.ctmpl"),
|
||||||
|
Destination: pointerutil.StringPtr("/path/on/disk/where/template/will/render.txt"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"full": {
|
||||||
|
fixturePath: "./test-fixtures/config-template-full.hcl",
|
||||||
|
expectedTemplates: []*ctconfig.TemplateConfig{
|
||||||
|
&ctconfig.TemplateConfig{
|
||||||
|
Backup: pointerutil.BoolPtr(true),
|
||||||
|
Command: pointerutil.StringPtr("restart service foo"),
|
||||||
|
CommandTimeout: pointerutil.TimeDurationPtr("60s"),
|
||||||
|
Contents: pointerutil.StringPtr("{{ keyOrDefault \"service/redis/maxconns@east-aws\" \"5\" }}"),
|
||||||
|
CreateDestDirs: pointerutil.BoolPtr(true),
|
||||||
|
Destination: pointerutil.StringPtr("/path/on/disk/where/template/will/render.txt"),
|
||||||
|
ErrMissingKey: pointerutil.BoolPtr(true),
|
||||||
|
LeftDelim: pointerutil.StringPtr("<<"),
|
||||||
|
Perms: pointerutil.FileModePtr(0655),
|
||||||
|
RightDelim: pointerutil.StringPtr(">>"),
|
||||||
|
SandboxPath: pointerutil.StringPtr("/path/on/disk/where"),
|
||||||
|
|
||||||
|
Wait: &ctconfig.WaitConfig{
|
||||||
|
Min: pointerutil.TimeDurationPtr("10s"),
|
||||||
|
Max: pointerutil.TimeDurationPtr("40s"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"many": {
|
||||||
|
fixturePath: "./test-fixtures/config-template-many.hcl",
|
||||||
|
expectedTemplates: []*ctconfig.TemplateConfig{
|
||||||
|
&ctconfig.TemplateConfig{
|
||||||
|
Source: pointerutil.StringPtr("/path/on/disk/to/template.ctmpl"),
|
||||||
|
Destination: pointerutil.StringPtr("/path/on/disk/where/template/will/render.txt"),
|
||||||
|
ErrMissingKey: pointerutil.BoolPtr(false),
|
||||||
|
CreateDestDirs: pointerutil.BoolPtr(true),
|
||||||
|
Command: pointerutil.StringPtr("restart service foo"),
|
||||||
|
Perms: pointerutil.FileModePtr(0600),
|
||||||
|
},
|
||||||
|
&ctconfig.TemplateConfig{
|
||||||
|
Source: pointerutil.StringPtr("/path/on/disk/to/template2.ctmpl"),
|
||||||
|
Destination: pointerutil.StringPtr("/path/on/disk/where/template/will/render2.txt"),
|
||||||
|
Backup: pointerutil.BoolPtr(true),
|
||||||
|
Perms: pointerutil.FileModePtr(0755),
|
||||||
|
Wait: &ctconfig.WaitConfig{
|
||||||
|
Min: pointerutil.TimeDurationPtr("2s"),
|
||||||
|
Max: pointerutil.TimeDurationPtr("10s"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
config, err := LoadConfig(tc.fixturePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := &Config{
|
||||||
|
AutoAuth: &AutoAuth{
|
||||||
|
Method: &Method{
|
||||||
|
Type: "aws",
|
||||||
|
MountPath: "auth/aws",
|
||||||
|
Namespace: "my-namespace/",
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"role": "foobar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Sinks: []*Sink{
|
||||||
|
&Sink{
|
||||||
|
Type: "file",
|
||||||
|
DHType: "curve25519",
|
||||||
|
DHPath: "/tmp/file-foo-dhpath",
|
||||||
|
AAD: "foobar",
|
||||||
|
Config: map[string]interface{}{
|
||||||
|
"path": "/tmp/file-foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Templates: tc.expectedTemplates,
|
||||||
|
PidFile: "./pidfile",
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff := deep.Equal(config, expected); diff != nil {
|
||||||
|
t.Fatal(diff)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
49
command/agent/config/test-fixtures/config-template-full.hcl
Normal file
49
command/agent/config/test-fixtures/config-template-full.hcl
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
pid_file = "./pidfile"
|
||||||
|
|
||||||
|
auto_auth {
|
||||||
|
method {
|
||||||
|
type = "aws"
|
||||||
|
namespace = "/my-namespace"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
role = "foobar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sink {
|
||||||
|
type = "file"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
path = "/tmp/file-foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
aad = "foobar"
|
||||||
|
dh_type = "curve25519"
|
||||||
|
dh_path = "/tmp/file-foo-dhpath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
destination = "/path/on/disk/where/template/will/render.txt"
|
||||||
|
create_dest_dirs = true
|
||||||
|
contents = "{{ keyOrDefault \"service/redis/maxconns@east-aws\" \"5\" }}"
|
||||||
|
|
||||||
|
command = "restart service foo"
|
||||||
|
command_timeout = "60s"
|
||||||
|
|
||||||
|
error_on_missing_key = true
|
||||||
|
perms = 0655
|
||||||
|
backup = true
|
||||||
|
left_delimiter = "<<"
|
||||||
|
right_delimiter = ">>"
|
||||||
|
|
||||||
|
sandbox_path = "/path/on/disk/where"
|
||||||
|
wait {
|
||||||
|
min = "5s"
|
||||||
|
max = "30s"
|
||||||
|
}
|
||||||
|
wait {
|
||||||
|
min = "10s"
|
||||||
|
max = "40s"
|
||||||
|
}
|
||||||
|
}
|
||||||
50
command/agent/config/test-fixtures/config-template-many.hcl
Normal file
50
command/agent/config/test-fixtures/config-template-many.hcl
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
pid_file = "./pidfile"
|
||||||
|
|
||||||
|
auto_auth {
|
||||||
|
method {
|
||||||
|
type = "aws"
|
||||||
|
namespace = "/my-namespace"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
role = "foobar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sink {
|
||||||
|
type = "file"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
path = "/tmp/file-foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
aad = "foobar"
|
||||||
|
dh_type = "curve25519"
|
||||||
|
dh_path = "/tmp/file-foo-dhpath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
source = "/path/on/disk/to/template.ctmpl"
|
||||||
|
destination = "/path/on/disk/where/template/will/render.txt"
|
||||||
|
|
||||||
|
create_dest_dirs = true
|
||||||
|
|
||||||
|
command = "restart service foo"
|
||||||
|
|
||||||
|
error_on_missing_key = false
|
||||||
|
perms = 0600
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
source = "/path/on/disk/to/template2.ctmpl"
|
||||||
|
destination = "/path/on/disk/where/template/will/render2.txt"
|
||||||
|
|
||||||
|
perms = 0755
|
||||||
|
|
||||||
|
backup = true
|
||||||
|
|
||||||
|
wait {
|
||||||
|
min = "2s"
|
||||||
|
max = "10s"
|
||||||
|
}
|
||||||
|
}
|
||||||
29
command/agent/config/test-fixtures/config-template-min.hcl
Normal file
29
command/agent/config/test-fixtures/config-template-min.hcl
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
pid_file = "./pidfile"
|
||||||
|
|
||||||
|
auto_auth {
|
||||||
|
method {
|
||||||
|
type = "aws"
|
||||||
|
namespace = "/my-namespace"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
role = "foobar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sink {
|
||||||
|
type = "file"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
path = "/tmp/file-foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
aad = "foobar"
|
||||||
|
dh_type = "curve25519"
|
||||||
|
dh_path = "/tmp/file-foo-dhpath"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
source = "/path/on/disk/to/template.ctmpl"
|
||||||
|
destination = "/path/on/disk/where/template/will/render.txt"
|
||||||
|
}
|
||||||
8
command/agent/doc.go
Normal file
8
command/agent/doc.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Package agent implements a daemon mode of Vault designed to provide helper
|
||||||
|
features like auto-auth, caching, and templating.
|
||||||
|
|
||||||
|
Agent has it's own configuration stanza and operates as a proxy to a Vault
|
||||||
|
service.
|
||||||
|
*/
|
||||||
|
package agent
|
||||||
188
command/agent/template/template.go
Normal file
188
command/agent/template/template.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
// 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
|
||||||
|
}
|
||||||
159
command/agent/template/template_test.go
Normal file
159
command/agent/template/template_test.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
ctconfig "github.com/hashicorp/consul-template/config"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
|
"github.com/hashicorp/vault/command/agent/config"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/logging"
|
||||||
|
"github.com/hashicorp/vault/sdk/helper/pointerutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestNewServer is a simple test to make sure NewServer returns a Server and
|
||||||
|
// channel
|
||||||
|
func TestNewServer(t *testing.T) {
|
||||||
|
server := NewServer(&ServerConfig{})
|
||||||
|
if server == nil {
|
||||||
|
t.Fatal("nil server returned")
|
||||||
|
}
|
||||||
|
if server.UnblockCh == nil {
|
||||||
|
t.Fatal("nil blocking channel returned")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerRun(t *testing.T) {
|
||||||
|
// create http test server
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(handleRequest))
|
||||||
|
defer ts.Close()
|
||||||
|
tmpDir, err := ioutil.TempDir("", "agent-tests")
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
templates []*ctconfig.TemplateConfig
|
||||||
|
}{
|
||||||
|
"basic": {
|
||||||
|
templates: []*ctconfig.TemplateConfig{
|
||||||
|
&ctconfig.TemplateConfig{
|
||||||
|
Contents: pointerutil.StringPtr(templateContents),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// secretRender is a simple struct that represents the secret we render to
|
||||||
|
// disk. It's used to unmarshal the file contents and test against
|
||||||
|
type secretRender struct {
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
Version string `json:"version"`
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
templateTokenCh := make(chan string, 1)
|
||||||
|
for i, template := range tc.templates {
|
||||||
|
dstFile := fmt.Sprintf("%s/render_%d.txt", tmpDir, i)
|
||||||
|
template.Destination = pointerutil.StringPtr(dstFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||||
|
sc := ServerConfig{
|
||||||
|
Logger: logging.NewVaultLogger(hclog.Trace),
|
||||||
|
VaultConf: &config.Vault{
|
||||||
|
Address: ts.URL,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var server *Server
|
||||||
|
server = NewServer(&sc)
|
||||||
|
if ts == nil {
|
||||||
|
t.Fatal("nil server returned")
|
||||||
|
}
|
||||||
|
if server.UnblockCh == nil {
|
||||||
|
t.Fatal("nil blocking channel returned")
|
||||||
|
}
|
||||||
|
|
||||||
|
go server.Run(ctx, templateTokenCh, tc.templates)
|
||||||
|
|
||||||
|
// send a dummy value to trigger the internal Runner to query for secret
|
||||||
|
// info
|
||||||
|
templateTokenCh <- "test"
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
case <-server.UnblockCh:
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel to clean things up
|
||||||
|
cancelFunc()
|
||||||
|
|
||||||
|
// verify test file exists and has the content we're looking for
|
||||||
|
for _, template := range tc.templates {
|
||||||
|
if template.Destination == nil {
|
||||||
|
t.Fatal("nil template destination")
|
||||||
|
}
|
||||||
|
content, err := ioutil.ReadFile(*template.Destination)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secret := secretRender{}
|
||||||
|
if err := json.Unmarshal(content, &secret); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if secret.Username != "appuser" || secret.Password != "password" || secret.Version != "3" {
|
||||||
|
t.Fatalf("secret didn't match: %#v", secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Fprintln(w, jsonResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonResponse = `
|
||||||
|
{
|
||||||
|
"request_id": "8af096e9-518c-7351-eff5-5ba20554b21f",
|
||||||
|
"lease_id": "",
|
||||||
|
"renewable": false,
|
||||||
|
"lease_duration": 0,
|
||||||
|
"data": {
|
||||||
|
"data": {
|
||||||
|
"password": "password",
|
||||||
|
"username": "appuser"
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"created_time": "2019-10-07T22:18:44.233247Z",
|
||||||
|
"deletion_time": "",
|
||||||
|
"destroyed": false,
|
||||||
|
"version": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"wrap_info": null,
|
||||||
|
"warnings": null,
|
||||||
|
"auth": null
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
var templateContents = `
|
||||||
|
{{ with secret "kv/myapp/config"}}
|
||||||
|
{
|
||||||
|
{{ if .Data.data.username}}"username":"{{ .Data.data.username}}",{{ end }}
|
||||||
|
{{ if .Data.data.password }}"password":"{{ .Data.data.password }}",{{ end }}
|
||||||
|
{{ if .Data.metadata.version}}"version":"{{ .Data.metadata.version }}"{{ end }}
|
||||||
|
}
|
||||||
|
{{ end }}
|
||||||
|
`
|
||||||
3
go.mod
3
go.mod
@ -48,7 +48,8 @@ require (
|
|||||||
github.com/google/go-github v17.0.0+incompatible
|
github.com/google/go-github v17.0.0+incompatible
|
||||||
github.com/google/go-metrics-stackdriver v0.0.0-20190816035513-b52628e82e2a
|
github.com/google/go-metrics-stackdriver v0.0.0-20190816035513-b52628e82e2a
|
||||||
github.com/google/go-querystring v1.0.0 // indirect
|
github.com/google/go-querystring v1.0.0 // indirect
|
||||||
github.com/hashicorp/consul/api v1.0.1
|
github.com/hashicorp/consul-template v0.22.0
|
||||||
|
github.com/hashicorp/consul/api v1.1.0
|
||||||
github.com/hashicorp/errwrap v1.0.0
|
github.com/hashicorp/errwrap v1.0.0
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1
|
github.com/hashicorp/go-cleanhttp v0.5.1
|
||||||
github.com/hashicorp/go-gcp-common v0.5.0
|
github.com/hashicorp/go-gcp-common v0.5.0
|
||||||
|
|||||||
29
go.sum
29
go.sum
@ -16,6 +16,7 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
|
|||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Azure/go-autorest v11.7.1+incompatible h1:M2YZIajBBVekV86x0rr1443Lc1F/Ylxb9w+5EtSyX3Q=
|
github.com/Azure/go-autorest v11.7.1+incompatible h1:M2YZIajBBVekV86x0rr1443Lc1F/Ylxb9w+5EtSyX3Q=
|
||||||
github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
github.com/Azure/go-autorest v11.7.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4=
|
github.com/DataDog/datadog-go v2.2.0+incompatible h1:V5BKkxACZLjzHjSgBbr2gvLA2Ae49yhc6CSY7MLy5k4=
|
||||||
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
|
||||||
@ -151,6 +152,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
|||||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
|
github.com/frankban/quicktest v1.4.0/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ=
|
||||||
github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg=
|
github.com/frankban/quicktest v1.4.1 h1:Wv2VwvNn73pAdFIVUQRXYDFp31lXKbqblIXo/Q5GPSg=
|
||||||
github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ=
|
github.com/frankban/quicktest v1.4.1/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ=
|
||||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||||
@ -213,6 +215,8 @@ github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a h1:ZJu5NB1Bk5ms4vw0Xu
|
|||||||
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180124185431-e89373fe6b4a/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
|
||||||
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||||
@ -260,10 +264,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.8.5 h1:2+KSC78XiO6Qy0hIjfc1OD9H+hsaJdJ
|
|||||||
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
github.com/hashicorp/consul/api v1.0.1 h1:LkHu3cLXjya4lgrAyZVe/CUBXgJ7AcDWKSeCjAYN9w0=
|
github.com/hashicorp/consul-template v0.22.0 h1:ti5cqAekOeMfFYLJCjlPtKGwBcqwVxoZO/Y2vctwuUE=
|
||||||
github.com/hashicorp/consul/api v1.0.1/go.mod h1:LQlewHPiuaRhn1mP2XE4RrjnlRgOeWa/ZM0xWLCen2M=
|
github.com/hashicorp/consul-template v0.22.0/go.mod h1:lHrykBIcPobCuEcIMLJryKxDyk2lUMnQWmffOEONH0k=
|
||||||
github.com/hashicorp/consul/sdk v0.1.0 h1:tTfutTNVUTDXpNM4YCImLfiiY3yCDpfgS6tNlUioIUE=
|
github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA=
|
||||||
github.com/hashicorp/consul/sdk v0.1.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||||
github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY=
|
github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY=
|
||||||
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
|
||||||
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
|
||||||
@ -271,6 +275,8 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv
|
|||||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||||
|
github.com/hashicorp/go-gatedio v0.5.0 h1:Jm1X5yP4yCqqWj5L1TgW7iZwCVPGtVc+mro5r/XX7Tg=
|
||||||
|
github.com/hashicorp/go-gatedio v0.5.0/go.mod h1:Lr3t8L6IyxD3DAeaUxGcgl2JnRUpWMCsmBl4Omu/2t4=
|
||||||
github.com/hashicorp/go-gcp-common v0.5.0 h1:kkIQTjNTopn4eXQ1+lCiHYZXUtgIZvbc6YtAQkMnTos=
|
github.com/hashicorp/go-gcp-common v0.5.0 h1:kkIQTjNTopn4eXQ1+lCiHYZXUtgIZvbc6YtAQkMnTos=
|
||||||
github.com/hashicorp/go-gcp-common v0.5.0/go.mod h1:IDGUI2N/OS3PiU4qZcXJeWKPI6O/9Y8hOrbSiMcqyYw=
|
github.com/hashicorp/go-gcp-common v0.5.0/go.mod h1:IDGUI2N/OS3PiU4qZcXJeWKPI6O/9Y8hOrbSiMcqyYw=
|
||||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||||
@ -329,8 +335,9 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||||
github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M=
|
|
||||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
|
github.com/hashicorp/memberlist v0.1.4 h1:gkyML/r71w3FL8gUi74Vk76avkj/9lYAY9lvg0OcoGs=
|
||||||
|
github.com/hashicorp/memberlist v0.1.4/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||||
github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf h1:U/40PQvWkaXCDdK9QHKf1pVDVcA+NIDVbzzonFGkgIA=
|
github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf h1:U/40PQvWkaXCDdK9QHKf1pVDVcA+NIDVbzzonFGkgIA=
|
||||||
github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf/go.mod h1:BDngVi1f4UA6aJq9WYTgxhfWSE1+42xshvstLU2fRGk=
|
github.com/hashicorp/nomad/api v0.0.0-20190412184103-1c38ced33adf/go.mod h1:BDngVi1f4UA6aJq9WYTgxhfWSE1+42xshvstLU2fRGk=
|
||||||
github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI=
|
github.com/hashicorp/raft v1.0.1/go.mod h1:DVSAWItjLjTOkVbSpWQ0j0kUADIvDaCtBxIcbNAQLkI=
|
||||||
@ -341,6 +348,8 @@ github.com/hashicorp/raft-snapshot v1.0.2-0.20190827162939-8117efcc5aab h1:WzGMw
|
|||||||
github.com/hashicorp/raft-snapshot v1.0.2-0.20190827162939-8117efcc5aab/go.mod h1:5sL9eUn72lH5DzsFIJ9jaysITbHksSSszImWSOTC8Ic=
|
github.com/hashicorp/raft-snapshot v1.0.2-0.20190827162939-8117efcc5aab/go.mod h1:5sL9eUn72lH5DzsFIJ9jaysITbHksSSszImWSOTC8Ic=
|
||||||
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
|
github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0=
|
||||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||||
|
github.com/hashicorp/serf v0.8.3 h1:MWYcmct5EtKz0efYooPcL0yNkem+7kWxqXDi/UIh+8k=
|
||||||
|
github.com/hashicorp/serf v0.8.3/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k=
|
||||||
github.com/hashicorp/vault-plugin-auth-alicloud v0.5.2-0.20190814210027-93970f08f2ec h1:HXVE8h6RXFsPJgwWpE+5CscsgekqtX4nhDlZGV9jEe4=
|
github.com/hashicorp/vault-plugin-auth-alicloud v0.5.2-0.20190814210027-93970f08f2ec h1:HXVE8h6RXFsPJgwWpE+5CscsgekqtX4nhDlZGV9jEe4=
|
||||||
github.com/hashicorp/vault-plugin-auth-alicloud v0.5.2-0.20190814210027-93970f08f2ec/go.mod h1:TYFfVFgKF9x92T7uXouI9rLPkNnyXo/KkNcj5t+mjdM=
|
github.com/hashicorp/vault-plugin-auth-alicloud v0.5.2-0.20190814210027-93970f08f2ec/go.mod h1:TYFfVFgKF9x92T7uXouI9rLPkNnyXo/KkNcj5t+mjdM=
|
||||||
github.com/hashicorp/vault-plugin-auth-azure v0.5.2-0.20190814210035-08e00d801115 h1:E57y918o+c+NoI5k7ohbpZu7vRm1XZKZfC5VQVpJvDI=
|
github.com/hashicorp/vault-plugin-auth-azure v0.5.2-0.20190814210035-08e00d801115 h1:E57y918o+c+NoI5k7ohbpZu7vRm1XZKZfC5VQVpJvDI=
|
||||||
@ -438,6 +447,8 @@ github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
|
|||||||
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
|
github.com/mattn/go-shellwords v1.0.5 h1:JhhFTIOslh5ZsPrpa3Wdg8bF0WI3b44EMblmU9wIsXc=
|
||||||
|
github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.0 h1:YNOwxxSJzSUARoD9KRZLzM9Y858MNGCOACTvCW9TSAc=
|
github.com/matttproud/golang_protobuf_extensions v1.0.0 h1:YNOwxxSJzSUARoD9KRZLzM9Y858MNGCOACTvCW9TSAc=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
github.com/matttproud/golang_protobuf_extensions v1.0.0/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||||
@ -446,8 +457,9 @@ github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1w
|
|||||||
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
|
github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU=
|
||||||
github.com/michaelklishin/rabbit-hole v1.5.0 h1:Bex27BiFDsijCM9D0ezSHqyy0kehpYHuNKaPqq/a4RM=
|
github.com/michaelklishin/rabbit-hole v1.5.0 h1:Bex27BiFDsijCM9D0ezSHqyy0kehpYHuNKaPqq/a4RM=
|
||||||
github.com/michaelklishin/rabbit-hole v1.5.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk=
|
github.com/michaelklishin/rabbit-hole v1.5.0/go.mod h1:vvI1uOitYZi0O5HEGXhaWC1XT80Gy+HvFheJ+5Krlhk=
|
||||||
github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
|
|
||||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
|
github.com/miekg/dns v1.1.15 h1:CSSIDtllwGLMoA6zjdKnaE6Tx6eVUxQ29LUgGetiDCI=
|
||||||
|
github.com/miekg/dns v1.1.15/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||||
github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
|
github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y=
|
||||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||||
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
|
||||||
@ -460,6 +472,8 @@ github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdI
|
|||||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||||
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
|
||||||
|
github.com/mitchellh/hashstructure v1.0.0 h1:ZkRJX1CyOoTkar7p/mLS5TZU4nJ1Rn/F8u9dGS02Q3Y=
|
||||||
|
github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ=
|
||||||
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY=
|
||||||
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
@ -515,6 +529,7 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
|
|||||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
|
github.com/pierrec/lz4 v2.2.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw=
|
github.com/pierrec/lz4 v2.2.6+incompatible h1:6aCX4/YZ9v8q69hTyiR7dNLnTA3fgtKHVVW5BCd5Znw=
|
||||||
github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
@ -678,6 +693,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2eP
|
|||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA=
|
||||||
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
@ -719,6 +735,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5 h1:sM3evRHxE/1RuMe1FYAL3j7C7fUfIjkbE+NiDAYUF8U=
|
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5 h1:sM3evRHxE/1RuMe1FYAL3j7C7fUfIjkbE+NiDAYUF8U=
|
||||||
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190523142557-0e01d883c5c5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20190730183949-1393eb018365/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||||
|
|||||||
28
sdk/helper/pointerutil/pointer.go
Normal file
28
sdk/helper/pointerutil/pointer.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package pointerutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StringPtr returns a pointer to a string value
|
||||||
|
func StringPtr(s string) *string {
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolPtr returns a pointer to a boolean value
|
||||||
|
func BoolPtr(b bool) *bool {
|
||||||
|
return &b
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimeDurationPtr returns a pointer to a time duration value
|
||||||
|
func TimeDurationPtr(duration string) *time.Duration {
|
||||||
|
d, _ := time.ParseDuration(duration)
|
||||||
|
|
||||||
|
return &d
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileModePtr returns a pointer to the given os.FileMode
|
||||||
|
func FileModePtr(o os.FileMode) *os.FileMode {
|
||||||
|
return &o
|
||||||
|
}
|
||||||
@ -54,6 +54,8 @@ These are the currently-available general configuration option:
|
|||||||
with code `0` after a single successful auth, where success means that a
|
with code `0` after a single successful auth, where success means that a
|
||||||
token was retrieved and all sinks successfully wrote it
|
token was retrieved and all sinks successfully wrote it
|
||||||
|
|
||||||
|
- `template` <tt>([template][template`]: \<optional\>)</tt> - Specifies options used for templating Vault secrets to files.
|
||||||
|
|
||||||
### vault Stanza
|
### vault Stanza
|
||||||
|
|
||||||
There can at most be one top level `vault` block and it has the following
|
There can at most be one top level `vault` block and it has the following
|
||||||
@ -150,10 +152,21 @@ listener "tcp" {
|
|||||||
address = "127.0.0.1:8100"
|
address = "127.0.0.1:8100"
|
||||||
tls_disable = true
|
tls_disable = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
source = "/etc/vault/server.key.ctmpl"
|
||||||
|
destination = "/etc/vault/server.key"
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
source = "/etc/vault/server.crt.ctmpl"
|
||||||
|
destination = "/etc/vault/server.crt"
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
[vault]: /docs/agent/index.html#vault-stanza
|
[vault]: /docs/agent/index.html#vault-stanza
|
||||||
[autoauth]: /docs/agent/autoauth/index.html
|
[autoauth]: /docs/agent/autoauth/index.html
|
||||||
[caching]: /docs/agent/caching/index.html
|
[caching]: /docs/agent/caching/index.html
|
||||||
|
[template]: /docs/agent/template/index.html
|
||||||
[listener]: /docs/agent/index.html#listener-stanza
|
[listener]: /docs/agent/index.html#listener-stanza
|
||||||
[listener_main]: /docs/configuration/listener/tcp.html
|
[listener_main]: /docs/configuration/listener/tcp.html
|
||||||
|
|||||||
121
website/source/docs/agent/template/index.html.md
Normal file
121
website/source/docs/agent/template/index.html.md
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
layout: "docs"
|
||||||
|
page_title: "Vault Agent Templates"
|
||||||
|
sidebar_title: "TemplatesAuto-Auth"
|
||||||
|
sidebar_current: "docs-agent-templates"
|
||||||
|
description: |-
|
||||||
|
Vault Agent's Template functionality allows Vault secrets to be rendered to files
|
||||||
|
using Consul Template markup.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Vault Agent Templates
|
||||||
|
|
||||||
|
Vault Agent's Template functionality allows Vault secrets to be rendered to files
|
||||||
|
using Consul Template markup.
|
||||||
|
|
||||||
|
## Functionality
|
||||||
|
|
||||||
|
The `template` stanza configures the templating engine in the Vault agent for rendering
|
||||||
|
secrets to files using Consul Template markup language. Multiple `template` stanzas
|
||||||
|
can be defined to render multiple files.
|
||||||
|
|
||||||
|
When the agent is started with templating enabled, it will attempt to acquire a
|
||||||
|
Vault token using the configured Method. On failure, it will back off for a short
|
||||||
|
while (including some randomness to help prevent thundering herd scenarios) and
|
||||||
|
retry. On success, secrets defined in the templates will be retrieved from Vault and
|
||||||
|
rendered locally.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The top level `template` block has multiple configurations entries:
|
||||||
|
|
||||||
|
- `source` `(object: optional)` - Path on disk to use as the input template. This
|
||||||
|
option is required if not using the `contents` option.
|
||||||
|
- `destination` `(object: required)` - Path on disk where the rendered secrets should
|
||||||
|
be created. If the parent directories If the parent directories do not exist, Vault
|
||||||
|
Agent will attempt to create them, unless `create_dest_dirs` is false.
|
||||||
|
- `create_dest_dirs` `(object: required)` - This option tells Vault Agent to create
|
||||||
|
the parent directories of the destination path if they do not exist. The default
|
||||||
|
value is true.
|
||||||
|
- `contents` `(object: optional)` - This option allows embedding the contents of
|
||||||
|
a template in the configuration file rather then supplying the `source` path to
|
||||||
|
the template file. This is useful for short templates. This option is mutually
|
||||||
|
exclusive with the `source` option.
|
||||||
|
- `command` `(object: optional)` - This is the optional command to run when the
|
||||||
|
template is rendered. The command will only run if the resulting template changes.
|
||||||
|
The command must return within 30s (configurable), and it must have a successful
|
||||||
|
exit code. Vault Agent is not a replacement for a process monitor or init system.
|
||||||
|
- `command_timeout` `(object: optional)` - This is the maximum amount of time to
|
||||||
|
wait for the optional command to return. Default is 30s.
|
||||||
|
- `error_on_missing_key` `(object: optional)` - Exit with an error when accessing
|
||||||
|
a struct or map field/key that does notexist. The default behavior will print "<no value>"
|
||||||
|
when accessing a field that does not exist. It is highly recommended you set this
|
||||||
|
to "true".
|
||||||
|
- `perms` `(object: optional)` - This is the permission to render the file. If
|
||||||
|
this option is left unspecified, Vault Agent will attempt to match the permissions
|
||||||
|
of the file that already exists at the destination path. If no file exists at that
|
||||||
|
path, the permissions are 0644.
|
||||||
|
- `backup` `(object: optional)` - This option backs up the previously rendered template
|
||||||
|
at the destination path before writing a new one. It keeps exactly one backup.
|
||||||
|
This option is useful for preventing accidental changes to the data without having
|
||||||
|
a rollback strategy.
|
||||||
|
- `left_delimiter` `(object: optional)` - Delimiter to use in the template. The
|
||||||
|
default is "{{" but for some templates, it may be easier to use a different
|
||||||
|
delimiter that does not conflict with the output file itself.
|
||||||
|
- `right_delimiter` `(object: optional)` - Delimiter to use in the template. The
|
||||||
|
default is "}}" but for some templates, it may be easier to use a different
|
||||||
|
delimiter that does not conflict with the output file itself.
|
||||||
|
- `sandbox_path` `(object: optional)` - If a sandbox path is provided, any path
|
||||||
|
provided to the `file` function is checked that it falls within the sandbox path.
|
||||||
|
Relative paths that try to traverse outside the sandbox path will exit with an error.
|
||||||
|
- `wait` `(object: required)` - This is the `minimum(:maximum)` to wait before rendering
|
||||||
|
a new template to disk and triggering a command, separated by a colon (`:`).
|
||||||
|
|
||||||
|
## Example Template
|
||||||
|
|
||||||
|
Template with Vault Agent requires the use of the `secret` function from Consul Template.
|
||||||
|
The following is an example of a template that retrieves a generic secret from Vault's
|
||||||
|
KV store:
|
||||||
|
|
||||||
|
```
|
||||||
|
{{ with secret "secret/my-secret" }}
|
||||||
|
{{ .Data.data.foo }}
|
||||||
|
{{ end }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Configuration
|
||||||
|
|
||||||
|
The following demonstrates configuring Vault Agent to template secrets using the
|
||||||
|
AppRole Auth method:
|
||||||
|
|
||||||
|
```python
|
||||||
|
pid_file = "./pidfile"
|
||||||
|
|
||||||
|
vault {
|
||||||
|
address = "https://127.0.0.1:8200"
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_auth {
|
||||||
|
method {
|
||||||
|
type = "approle"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
role_id_file_path = "/etc/vault/roleid"
|
||||||
|
secret_id_file_path = "/etc/vault/secretid"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sink {
|
||||||
|
type = "file"
|
||||||
|
|
||||||
|
config = {
|
||||||
|
path = "/tmp/file-foo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
source = "/tmp/agent/template.ctmpl"
|
||||||
|
destination = "/tmp/agent/render.txt"
|
||||||
|
}
|
||||||
|
```
|
||||||
Loading…
x
Reference in New Issue
Block a user