Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vault Agent Template #7652

Merged
merged 17 commits into from Oct 18, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
139 changes: 100 additions & 39 deletions command/agent.go
Expand Up @@ -29,10 +29,12 @@ import (
"github.com/hashicorp/vault/command/agent/auth/jwt"
"github.com/hashicorp/vault/command/agent/auth/kubernetes"
"github.com/hashicorp/vault/command/agent/cache"
"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/file"
"github.com/hashicorp/vault/command/agent/sink/inmem"
"github.com/hashicorp/vault/command/agent/template"
gatedwriter "github.com/hashicorp/vault/helper/gated-writer"
"github.com/hashicorp/vault/sdk/helper/consts"
"github.com/hashicorp/vault/sdk/helper/logging"
Expand Down Expand Up @@ -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")
}

if config.Vault != nil {
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,
})
// create an empty Vault configuration if none was loaded from file. The
// follow-up setStringFlag calls will populate with defaults if otherwise
// omitted
if config.Vault == nil {
config.Vault = new(agentConfig.Vault)
}
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)
info := make(map[string]string)
Expand Down Expand Up @@ -294,10 +300,14 @@ func (c *AgentCommand) Run(args []string) int {
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())

var method auth.AuthMethod
var sinks []*sink.SinkConfig
var namespace string
if config.AutoAuth != nil {
for _, sc := range config.AutoAuth.Sinks {
switch sc.Type {
Expand Down Expand Up @@ -327,7 +337,8 @@ func (c *AgentCommand) Run(args []string) int {
// Check if a default namespace has been set
mountPath := config.AutoAuth.Method.MountPath
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{
Expand Down Expand Up @@ -486,14 +497,21 @@ func (c *AgentCommand) Run(args []string) int {
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
if method != nil {
enableTokenCh := len(config.Templates) > 0
ah := auth.NewAuthHandler(&auth.AuthHandlerConfig{
Logger: c.logger.Named("auth.handler"),
Client: c.client,
WrapTTL: config.AutoAuth.Method.WrapTTL,
EnableReauthOnNewCredentials: config.AutoAuth.EnableReauthOnNewCredentials,
EnableTemplateTokenCh: enableTokenCh,
})
ahDoneCh = ah.DoneCh

Expand All @@ -504,8 +522,20 @@ func (c *AgentCommand) Run(args []string) int {
})
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 ss.Run(ctx, ah.OutputCh, sinks)
go ts.Run(ctx, ah.TemplateTokenCh, config.Templates)
}

// Server configuration output
Expand Down Expand Up @@ -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 {
case <-ssDoneCh:
// This will happen if we exit-on-auth
Expand All @@ -549,6 +588,10 @@ func (c *AgentCommand) Run(args []string) int {
if ssDoneCh != nil {
<-ssDoneCh
}

if tsDoneCh != nil {
<-tsDoneCh
}
}

return 0
Expand Down Expand Up @@ -648,3 +691,21 @@ func (c *AgentCommand) removePidFile(pidPath string) error {
}
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 changes: 15 additions & 0 deletions command/agent/README.md
@@ -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/
12 changes: 12 additions & 0 deletions command/agent/auth/auth.go
Expand Up @@ -30,18 +30,21 @@ type AuthConfig struct {
type AuthHandler struct {
DoneCh chan struct{}
OutputCh chan string
TemplateTokenCh chan string
logger hclog.Logger
client *api.Client
random *rand.Rand
wrapTTL time.Duration
enableReauthOnNewCredentials bool
enableTemplateTokenCh bool
}

type AuthHandlerConfig struct {
Logger hclog.Logger
Client *api.Client
WrapTTL time.Duration
EnableReauthOnNewCredentials bool
EnableTemplateTokenCh bool
}

func NewAuthHandler(conf *AuthHandlerConfig) *AuthHandler {
Expand All @@ -50,11 +53,13 @@ func NewAuthHandler(conf *AuthHandlerConfig) *AuthHandler {
// 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
OutputCh: make(chan string, 1),
TemplateTokenCh: make(chan string, 1),
logger: conf.Logger,
client: conf.Client,
random: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
wrapTTL: conf.WrapTTL,
enableReauthOnNewCredentials: conf.EnableReauthOnNewCredentials,
enableTemplateTokenCh: conf.EnableTemplateTokenCh,
}

return ah
Expand All @@ -77,6 +82,7 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) {
am.Shutdown()
close(ah.OutputCh)
close(ah.DoneCh)
close(ah.TemplateTokenCh)
ah.logger.Info("auth handler stopped")
}()

Expand Down Expand Up @@ -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.OutputCh <- string(wrappedResp)
if ah.enableTemplateTokenCh {
ah.TemplateTokenCh <- string(wrappedResp)
}

am.CredSuccess()

Expand All @@ -189,6 +198,9 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) {
}
ah.logger.Info("authentication successful, sending token to sinks")
ah.OutputCh <- secret.Auth.ClientToken
if ah.enableTemplateTokenCh {
ah.TemplateTokenCh <- secret.Auth.ClientToken
}

am.CredSuccess()
}
Expand Down
3 changes: 2 additions & 1 deletion command/agent/auth/auth_test.go
Expand Up @@ -87,7 +87,8 @@ consumption:
for {
select {
case <-ah.OutputCh:
// Nothing
case <-ah.TemplateTokenCh:
// Nothing
case <-time.After(stopTime.Sub(time.Now())):
if !closed {
cancelFunc()
Expand Down