Rework agent retry config, extend it to cover proxy cache as well (#11113)
Remove template_retry config section. Add new vault.retry section which only has num_retries field; if num_retries is 0 or absent, default it to 12 for backwards compat with pre-1.7 template retrying. Setting num_retries=-1 disables retries. Configured retries are used for both templating and api proxy, though if template requests go through proxy (currently requires persistence enabled) we'll only configure retries for the latter to avoid duplicate retrying. Though there is some duplicate retrying already because whenever the template server does a retry when not going through the proxy, the Vault client it uses allows for 2 behind-the-scenes retries for some 400/500 http error codes.
This commit is contained in:
parent
5353279e75
commit
9c5f018938
|
@ -0,0 +1,3 @@
|
|||
```changelog:enhancement
|
||||
agent: Add a vault.retry stanza that allows specifying number of retries on failure; this applies both to templating and proxied requests.
|
||||
```
|
|
@ -234,13 +234,6 @@ func (c *AgentCommand) Run(args []string) int {
|
|||
c.UI.Info("No auto_auth block found in config file, not starting automatic authentication feature")
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
exitAfterAuth := config.ExitAfterAuth
|
||||
f.Visit(func(fl *flag.Flag) {
|
||||
if fl.Name == "exit-after-auth" {
|
||||
|
@ -421,6 +414,13 @@ func (c *AgentCommand) Run(args []string) int {
|
|||
}
|
||||
}
|
||||
|
||||
// We do this after auto-auth has been configured, because we don't want to
|
||||
// confuse the issue of retries for auth failures which have their own
|
||||
// config and are handled a bit differently.
|
||||
if os.Getenv(api.EnvVaultMaxRetries) == "" {
|
||||
client.SetMaxRetries(config.Vault.Retry.NumRetries)
|
||||
}
|
||||
|
||||
enforceConsistency := cache.EnforceConsistencyNever
|
||||
whenInconsistent := cache.WhenInconsistentFail
|
||||
if config.Cache != nil {
|
||||
|
@ -721,9 +721,8 @@ func (c *AgentCommand) Run(args []string) int {
|
|||
}
|
||||
|
||||
// Inform any tests that the server is ready
|
||||
select {
|
||||
case c.startedCh <- struct{}{}:
|
||||
default:
|
||||
if c.startedCh != nil {
|
||||
close(c.startedCh)
|
||||
}
|
||||
|
||||
// Listen for signals
|
||||
|
|
|
@ -28,7 +28,10 @@ type Config struct {
|
|||
Cache *Cache `hcl:"cache"`
|
||||
Vault *Vault `hcl:"vault"`
|
||||
Templates []*ctconfig.TemplateConfig `hcl:"templates"`
|
||||
TemplateRetry *TemplateRetry `hcl:"template_retry"`
|
||||
}
|
||||
|
||||
type Retry struct {
|
||||
NumRetries int `hcl:"num_retries"`
|
||||
}
|
||||
|
||||
// Vault contains configuration for connecting to Vault servers
|
||||
|
@ -41,6 +44,7 @@ type Vault struct {
|
|||
ClientCert string `hcl:"client_cert"`
|
||||
ClientKey string `hcl:"client_key"`
|
||||
TLSServerName string `hcl:"tls_server_name"`
|
||||
Retry *Retry `hcl:"retry"`
|
||||
}
|
||||
|
||||
// Cache contains any configuration needed for Cache mode
|
||||
|
@ -97,15 +101,6 @@ type Sink struct {
|
|||
Config map[string]interface{}
|
||||
}
|
||||
|
||||
type TemplateRetry struct {
|
||||
Enabled bool `hcl:"enabled"`
|
||||
Attempts int `hcl:"attempts"`
|
||||
BackoffRaw interface{} `hcl:"backoff"`
|
||||
Backoff time.Duration `hcl:"-"`
|
||||
MaxBackoffRaw interface{} `hcl:"max_backoff"`
|
||||
MaxBackoff time.Duration `hcl:"-"`
|
||||
}
|
||||
|
||||
func NewConfig() *Config {
|
||||
return &Config{
|
||||
SharedConfig: new(configutil.SharedConfig),
|
||||
|
@ -193,8 +188,19 @@ func LoadConfig(path string) (*Config, error) {
|
|||
return nil, errwrap.Wrapf("error parsing 'vault':{{err}}", err)
|
||||
}
|
||||
|
||||
if err := parseRetry(result, list); err != nil {
|
||||
return nil, errwrap.Wrapf("error parsing 'retry': {{err}}", err)
|
||||
if result.Vault == nil {
|
||||
result.Vault = &Vault{}
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
if result.Vault.Retry == nil {
|
||||
result.Vault.Retry = &Retry{}
|
||||
}
|
||||
switch result.Vault.Retry.NumRetries {
|
||||
case 0:
|
||||
result.Vault.Retry.NumRetries = ctconfig.DefaultRetryAttempts
|
||||
case -1:
|
||||
result.Vault.Retry.NumRetries = 0
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
@ -229,11 +235,20 @@ func parseVault(result *Config, list *ast.ObjectList) error {
|
|||
|
||||
result.Vault = &v
|
||||
|
||||
subs, ok := item.Val.(*ast.ObjectType)
|
||||
if !ok {
|
||||
return fmt.Errorf("could not parse %q as an object", name)
|
||||
}
|
||||
|
||||
if err := parseRetry(result, subs.List); err != nil {
|
||||
return errwrap.Wrapf("error parsing 'retry': {{err}}", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRetry(result *Config, list *ast.ObjectList) error {
|
||||
name := "template_retry"
|
||||
name := "retry"
|
||||
|
||||
retryList := list.Filter(name)
|
||||
if len(retryList.Items) == 0 {
|
||||
|
@ -246,36 +261,13 @@ func parseRetry(result *Config, list *ast.ObjectList) error {
|
|||
|
||||
item := retryList.Items[0]
|
||||
|
||||
var r TemplateRetry
|
||||
var r Retry
|
||||
err := hcl.DecodeObject(&r, item.Val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set defaults
|
||||
if r.Attempts < 1 {
|
||||
r.Attempts = ctconfig.DefaultRetryAttempts
|
||||
}
|
||||
r.Backoff = ctconfig.DefaultRetryBackoff
|
||||
r.MaxBackoff = ctconfig.DefaultRetryMaxBackoff
|
||||
|
||||
if r.BackoffRaw != nil {
|
||||
var err error
|
||||
if r.Backoff, err = parseutil.ParseDurationSecond(r.BackoffRaw); err != nil {
|
||||
return err
|
||||
}
|
||||
r.BackoffRaw = nil
|
||||
}
|
||||
|
||||
if r.MaxBackoffRaw != nil {
|
||||
var err error
|
||||
if r.MaxBackoff, err = parseutil.ParseDurationSecond(r.MaxBackoffRaw); err != nil {
|
||||
return err
|
||||
}
|
||||
r.MaxBackoffRaw = nil
|
||||
}
|
||||
|
||||
result.TemplateRetry = &r
|
||||
result.Vault.Retry = &r
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -82,6 +82,9 @@ func TestLoadConfigFile_AgentCache(t *testing.T) {
|
|||
TLSSkipVerify: true,
|
||||
ClientCert: "config_client_cert",
|
||||
ClientKey: "config_client_key",
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -158,6 +161,11 @@ func TestLoadConfigFile(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if diff := deep.Equal(config, expected); diff != nil {
|
||||
|
@ -203,6 +211,11 @@ func TestLoadConfigFile_Method_Wrapping(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if diff := deep.Equal(config, expected); diff != nil {
|
||||
|
@ -228,6 +241,11 @@ func TestLoadConfigFile_AgentCache_NoAutoAuth(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
config.Listeners[0].RawConfig = nil
|
||||
|
@ -316,6 +334,11 @@ func TestLoadConfigFile_AgentCache_AutoAuth_NoSink(t *testing.T) {
|
|||
UseAutoAuthTokenRaw: true,
|
||||
ForceAutoAuthToken: false,
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
config.Listeners[0].RawConfig = nil
|
||||
|
@ -355,6 +378,11 @@ func TestLoadConfigFile_AgentCache_AutoAuth_Force(t *testing.T) {
|
|||
UseAutoAuthTokenRaw: "force",
|
||||
ForceAutoAuthToken: true,
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
config.Listeners[0].RawConfig = nil
|
||||
|
@ -394,6 +422,11 @@ func TestLoadConfigFile_AgentCache_AutoAuth_True(t *testing.T) {
|
|||
UseAutoAuthTokenRaw: "true",
|
||||
ForceAutoAuthToken: false,
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
config.Listeners[0].RawConfig = nil
|
||||
|
@ -444,6 +477,11 @@ func TestLoadConfigFile_AgentCache_AutoAuth_False(t *testing.T) {
|
|||
UseAutoAuthTokenRaw: "false",
|
||||
ForceAutoAuthToken: false,
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
config.Listeners[0].RawConfig = nil
|
||||
|
@ -478,6 +516,11 @@ func TestLoadConfigFile_AgentCache_Persist(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
config.Listeners[0].RawConfig = nil
|
||||
|
@ -593,6 +636,11 @@ func TestLoadConfigFile_Template(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
Templates: tc.expectedTemplates,
|
||||
}
|
||||
|
||||
|
@ -689,6 +737,11 @@ func TestLoadConfigFile_Template_NoSinks(t *testing.T) {
|
|||
Sinks: nil,
|
||||
},
|
||||
Templates: tc.expectedTemplates,
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if diff := deep.Equal(config, expected); diff != nil {
|
||||
|
@ -731,14 +784,9 @@ func TestLoadConfigFile_Vault_Retry(t *testing.T) {
|
|||
},
|
||||
Vault: &Vault{
|
||||
Address: "http://127.0.0.1:1111",
|
||||
},
|
||||
TemplateRetry: &TemplateRetry{
|
||||
Enabled: true,
|
||||
Attempts: 5,
|
||||
BackoffRaw: nil,
|
||||
Backoff: 100 * time.Millisecond,
|
||||
MaxBackoffRaw: nil,
|
||||
MaxBackoff: 400 * time.Millisecond,
|
||||
Retry: &Retry{
|
||||
NumRetries: 5,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -780,14 +828,9 @@ func TestLoadConfigFile_Vault_Retry_Empty(t *testing.T) {
|
|||
},
|
||||
Vault: &Vault{
|
||||
Address: "http://127.0.0.1:1111",
|
||||
},
|
||||
TemplateRetry: &TemplateRetry{
|
||||
Enabled: false,
|
||||
Attempts: ctconfig.DefaultRetryAttempts,
|
||||
BackoffRaw: nil,
|
||||
Backoff: ctconfig.DefaultRetryBackoff,
|
||||
MaxBackoffRaw: nil,
|
||||
MaxBackoff: ctconfig.DefaultRetryMaxBackoff,
|
||||
Retry: &Retry{
|
||||
ctconfig.DefaultRetryAttempts,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -817,6 +860,11 @@ func TestLoadConfigFile_EnforceConsistency(t *testing.T) {
|
|||
EnforceConsistency: "always",
|
||||
WhenInconsistent: "retry",
|
||||
},
|
||||
Vault: &Vault{
|
||||
Retry: &Retry{
|
||||
NumRetries: 12,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
config.Listeners[0].RawConfig = nil
|
||||
|
|
|
@ -23,6 +23,6 @@ auto_auth {
|
|||
|
||||
vault {
|
||||
address = "http://127.0.0.1:1111"
|
||||
retry {}
|
||||
}
|
||||
|
||||
template_retry {}
|
||||
|
|
|
@ -23,11 +23,7 @@ auto_auth {
|
|||
|
||||
vault {
|
||||
address = "http://127.0.0.1:1111"
|
||||
}
|
||||
|
||||
template_retry {
|
||||
enabled = true
|
||||
attempts = 5
|
||||
backoff = "100ms"
|
||||
max_backoff = "400ms"
|
||||
retry {
|
||||
num_retries = 5
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,10 +65,6 @@ type Server struct {
|
|||
|
||||
logger hclog.Logger
|
||||
exitAfterAuth bool
|
||||
|
||||
// testingLimitRetry is used for tests to limit the number of retries
|
||||
// performed by the template server
|
||||
testingLimitRetry int
|
||||
}
|
||||
|
||||
// NewServer returns a new configured server
|
||||
|
@ -164,19 +160,6 @@ func (ts *Server) Run(ctx context.Context, incoming chan string, templates []*ct
|
|||
},
|
||||
}
|
||||
|
||||
if ts.config.AgentConfig.TemplateRetry != nil && ts.config.AgentConfig.TemplateRetry.Enabled {
|
||||
ctv.Vault.Retry = &ctconfig.RetryConfig{
|
||||
Attempts: &ts.config.AgentConfig.TemplateRetry.Attempts,
|
||||
Backoff: &ts.config.AgentConfig.TemplateRetry.Backoff,
|
||||
MaxBackoff: &ts.config.AgentConfig.TemplateRetry.MaxBackoff,
|
||||
Enabled: &ts.config.AgentConfig.TemplateRetry.Enabled,
|
||||
}
|
||||
} else if ts.testingLimitRetry != 0 {
|
||||
// If we're testing, limit retries to 3 attempts to avoid
|
||||
// long test runs from exponential back-offs
|
||||
ctv.Vault.Retry = &ctconfig.RetryConfig{Attempts: &ts.testingLimitRetry}
|
||||
}
|
||||
|
||||
runnerConfig = runnerConfig.Merge(&ctv)
|
||||
var runnerErr error
|
||||
ts.runner, runnerErr = manager.NewRunner(runnerConfig, false)
|
||||
|
@ -255,8 +238,21 @@ func newRunnerConfig(sc *ServerConfig, templates ctconfig.TemplateConfigs) (*ctc
|
|||
ServerName: pointerutil.StringPtr(""),
|
||||
}
|
||||
|
||||
// The cache does its own retry management based on sc.AgentConfig.Retry,
|
||||
// so we only want to set this up for templating if we're not routing
|
||||
// templating through the cache. We do need to assign something to Retry
|
||||
// though or it will use its default of 12 retries.
|
||||
var attempts int
|
||||
if sc.AgentConfig.Vault != nil && sc.AgentConfig.Vault.Retry != nil {
|
||||
attempts = sc.AgentConfig.Vault.Retry.NumRetries
|
||||
}
|
||||
|
||||
// Use the cache if available or fallback to the Vault server values.
|
||||
// For now we're only routing templating through the cache when persistence
|
||||
// is enabled. The templating engine and the cache have some inconsistencies
|
||||
// that need to be fixed for 1.7x/1.8
|
||||
if sc.AgentConfig.Cache != nil && sc.AgentConfig.Cache.Persist != nil && len(sc.AgentConfig.Listeners) != 0 {
|
||||
attempts = 0
|
||||
scheme := "unix://"
|
||||
if sc.AgentConfig.Listeners[0].Type == "tcp" {
|
||||
scheme = "https://"
|
||||
|
@ -286,6 +282,11 @@ func newRunnerConfig(sc *ServerConfig, templates ctconfig.TemplateConfigs) (*ctc
|
|||
CaPath: &sc.AgentConfig.Vault.CAPath,
|
||||
}
|
||||
}
|
||||
enabled := attempts > 0
|
||||
conf.Vault.Retry = &ctconfig.RetryConfig{
|
||||
Attempts: &attempts,
|
||||
Enabled: &enabled,
|
||||
}
|
||||
|
||||
conf.Finalize()
|
||||
|
||||
|
|
|
@ -454,6 +454,9 @@ func TestServerRun(t *testing.T) {
|
|||
AgentConfig: &config.Config{
|
||||
Vault: &config.Vault{
|
||||
Address: ts.URL,
|
||||
Retry: &config.Retry{
|
||||
NumRetries: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
LogLevel: hclog.Trace,
|
||||
|
@ -466,7 +469,6 @@ func TestServerRun(t *testing.T) {
|
|||
if ts == nil {
|
||||
t.Fatal("nil server returned")
|
||||
}
|
||||
server.testingLimitRetry = 3
|
||||
|
||||
errCh := make(chan error)
|
||||
go func() {
|
||||
|
|
|
@ -501,12 +501,6 @@ auto_auth {
|
|||
secret_id_file_path = "%s"
|
||||
}
|
||||
}
|
||||
|
||||
sink "file" {
|
||||
config = {
|
||||
path = "%s"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cache {
|
||||
|
@ -1234,3 +1228,465 @@ func makeTempFile(t *testing.T, name, contents string) string {
|
|||
f.Close()
|
||||
return path
|
||||
}
|
||||
|
||||
// handler makes 500 errors happen for reads on /v1/secret.
|
||||
// Definitely not thread-safe, do not use t.Parallel with this.
|
||||
type handler struct {
|
||||
props *vault.HandlerProperties
|
||||
failCount int
|
||||
t *testing.T
|
||||
}
|
||||
|
||||
func (h *handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
||||
if req.Method == "GET" && strings.HasPrefix(req.URL.Path, "/v1/secret") {
|
||||
if h.failCount > 0 {
|
||||
h.failCount--
|
||||
h.t.Logf("%s failing GET request on %s, failures left: %d", time.Now(), req.URL.Path, h.failCount)
|
||||
resp.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
h.t.Logf("passing GET request on %s", req.URL.Path)
|
||||
}
|
||||
vaulthttp.Handler(h.props).ServeHTTP(resp, req)
|
||||
}
|
||||
|
||||
// TestAgent_Template_Retry verifies that the template server retries requests
|
||||
// based on retry configuration.
|
||||
func TestAgent_Template_Retry(t *testing.T) {
|
||||
//----------------------------------------------------
|
||||
// Start the server and agent
|
||||
//----------------------------------------------------
|
||||
logger := logging.NewVaultLogger(hclog.Trace)
|
||||
var h handler
|
||||
cluster := vault.NewTestCluster(t,
|
||||
&vault.CoreConfig{
|
||||
Logger: logger,
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
"approle": credAppRole.Factory,
|
||||
},
|
||||
LogicalBackends: map[string]logical.Factory{
|
||||
"kv": logicalKv.Factory,
|
||||
},
|
||||
},
|
||||
&vault.TestClusterOptions{
|
||||
NumCores: 1,
|
||||
HandlerFunc: func(properties *vault.HandlerProperties) http.Handler {
|
||||
h.props = properties
|
||||
h.t = t
|
||||
return &h
|
||||
},
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
vault.TestWaitActive(t, cluster.Cores[0].Core)
|
||||
serverClient := cluster.Cores[0].Client
|
||||
|
||||
// Unset the environment variable so that agent picks up the right test
|
||||
// cluster address
|
||||
defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress))
|
||||
os.Unsetenv(api.EnvVaultAddress)
|
||||
|
||||
methodConf, cleanup := prepAgentApproleKV(t, serverClient)
|
||||
defer cleanup()
|
||||
|
||||
err := serverClient.Sys().TuneMount("secret", api.MountConfigInput{
|
||||
Options: map[string]string{
|
||||
"version": "2",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = serverClient.Logical().Write("secret/data/otherapp", map[string]interface{}{
|
||||
"data": map[string]interface{}{
|
||||
"username": "barstuff",
|
||||
"password": "zap",
|
||||
"cert": "something",
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make a temp directory to hold renders. Each test will create a temp dir
|
||||
// inside this one
|
||||
tmpDirRoot, err := ioutil.TempDir("", "agent-test-renders")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDirRoot)
|
||||
|
||||
intRef := func(i int) *int {
|
||||
return &i
|
||||
}
|
||||
// start test cases here
|
||||
testCases := map[string]struct {
|
||||
retries *int
|
||||
expectError bool
|
||||
}{
|
||||
"none": {
|
||||
retries: intRef(-1),
|
||||
expectError: true,
|
||||
},
|
||||
"one": {
|
||||
retries: intRef(1),
|
||||
expectError: true,
|
||||
},
|
||||
"two": {
|
||||
retries: intRef(2),
|
||||
expectError: false,
|
||||
},
|
||||
"missing": {
|
||||
retries: nil,
|
||||
expectError: false,
|
||||
},
|
||||
"default": {
|
||||
retries: intRef(0),
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for tcname, tc := range testCases {
|
||||
t.Run(tcname, func(t *testing.T) {
|
||||
// We fail the first 6 times. The consul-template code creates
|
||||
// a Vault client with MaxRetries=2, so for every consul-template
|
||||
// retry configured, it will in practice make up to 3 requests.
|
||||
// Thus if consul-template is configured with "one" retry, it will
|
||||
// fail given our failCount, but if configured with "two" retries,
|
||||
// they will consume our 6th failure, and on the "third (from its
|
||||
// perspective) attempt, it will succeed.
|
||||
h.failCount = 6
|
||||
|
||||
// create temp dir for this test run
|
||||
tmpDir, err := ioutil.TempDir(tmpDirRoot, tcname)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// make some template files
|
||||
templatePath := filepath.Join(tmpDir, "render_0.tmpl")
|
||||
if err := ioutil.WriteFile(templatePath, []byte(templateContents(0)), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
templateConfig := fmt.Sprintf(templateConfigString, templatePath, tmpDir, "render_0.json")
|
||||
|
||||
var retryConf string
|
||||
if tc.retries != nil {
|
||||
retryConf = fmt.Sprintf("retry { num_retries = %d }", *tc.retries)
|
||||
}
|
||||
|
||||
config := fmt.Sprintf(`
|
||||
%s
|
||||
vault {
|
||||
address = "%s"
|
||||
%s
|
||||
tls_skip_verify = true
|
||||
}
|
||||
%s
|
||||
`, methodConf, serverClient.Address(), retryConf, templateConfig)
|
||||
|
||||
configPath := makeTempFile(t, "config.hcl", config)
|
||||
defer os.Remove(configPath)
|
||||
|
||||
// Start the agent
|
||||
_, cmd := testAgentCommand(t, logger)
|
||||
cmd.startedCh = make(chan struct{})
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
var code int
|
||||
go func() {
|
||||
code = cmd.Run([]string{"-config", configPath})
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-cmd.startedCh:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Errorf("timeout")
|
||||
}
|
||||
|
||||
verify := func() error {
|
||||
t.Helper()
|
||||
// We need to poll for a bit to give Agent time to render the
|
||||
// templates. Without this this, the test will attempt to read
|
||||
// the temp dir before Agent has had time to render and will
|
||||
// likely fail the test
|
||||
tick := time.Tick(1 * time.Second)
|
||||
timeout := time.After(10 * time.Second)
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case <-timeout:
|
||||
return fmt.Errorf("timed out waiting for templates to render, last error: %v", err)
|
||||
case <-tick:
|
||||
}
|
||||
// Check for files rendered in the directory and break
|
||||
// early for shutdown if we do have all the files
|
||||
// rendered
|
||||
|
||||
//----------------------------------------------------
|
||||
// Perform the tests
|
||||
//----------------------------------------------------
|
||||
|
||||
if numFiles := testListFiles(t, tmpDir, ".json"); numFiles != 1 {
|
||||
err = fmt.Errorf("expected 1 template, got (%d)", numFiles)
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := filepath.Join(tmpDir, "render_0.json")
|
||||
var c []byte
|
||||
c, err = ioutil.ReadFile(fileName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if string(c) != templateRendered(0) {
|
||||
err = fmt.Errorf("expected='%s', got='%s'", templateRendered(0), string(c))
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
err = verify()
|
||||
close(cmd.ShutdownCh)
|
||||
wg.Wait()
|
||||
|
||||
switch {
|
||||
case (code != 0 || err != nil) && tc.expectError:
|
||||
case code == 0 && err == nil && !tc.expectError:
|
||||
default:
|
||||
t.Fatalf("%s expectError=%v error=%v code=%d", tcname, tc.expectError, err, code)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// prepAgentApproleKV configures a Vault instance for approle authentication,
|
||||
// such that the resulting token will have global permissions across /kv
|
||||
// and /secret mounts. Returns the auto_auth config stanza to setup an Agent
|
||||
// to connect using approle.
|
||||
func prepAgentApproleKV(t *testing.T, client *api.Client) (string, func()) {
|
||||
t.Helper()
|
||||
|
||||
policyAutoAuthAppRole := `
|
||||
path "/kv/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
path "/secret/*" {
|
||||
capabilities = ["create", "read", "update", "delete", "list"]
|
||||
}
|
||||
`
|
||||
// Add an kv-admin policy
|
||||
if err := client.Sys().PutPolicy("test-autoauth", policyAutoAuthAppRole); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Enable approle
|
||||
err := client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
|
||||
Type: "approle",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.Logical().Write("auth/approle/role/test1", map[string]interface{}{
|
||||
"bind_secret_id": "true",
|
||||
"token_ttl": "3s",
|
||||
"token_max_ttl": "10s",
|
||||
"policies": []string{"test-autoauth"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
resp, err := client.Logical().Write("auth/approle/role/test1/secret-id", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
secretID := resp.Data["secret_id"].(string)
|
||||
secretIDFile := makeTempFile(t, "secret_id.txt", secretID+"\n")
|
||||
|
||||
resp, err = client.Logical().Read("auth/approle/role/test1/role-id")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roleID := resp.Data["role_id"].(string)
|
||||
roleIDFile := makeTempFile(t, "role_id.txt", roleID+"\n")
|
||||
|
||||
config := fmt.Sprintf(`
|
||||
auto_auth {
|
||||
method "approle" {
|
||||
mount_path = "auth/approle"
|
||||
config = {
|
||||
role_id_file_path = "%s"
|
||||
secret_id_file_path = "%s"
|
||||
remove_secret_id_file_after_reading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
`, roleIDFile, secretIDFile)
|
||||
|
||||
cleanup := func() {
|
||||
_ = os.Remove(roleIDFile)
|
||||
_ = os.Remove(secretIDFile)
|
||||
}
|
||||
return config, cleanup
|
||||
}
|
||||
|
||||
func TestAgent_Cache_Retry(t *testing.T) {
|
||||
//----------------------------------------------------
|
||||
// Start the server and agent
|
||||
//----------------------------------------------------
|
||||
logger := logging.NewVaultLogger(hclog.Trace)
|
||||
var h handler
|
||||
cluster := vault.NewTestCluster(t,
|
||||
&vault.CoreConfig{
|
||||
Logger: logger,
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
"approle": credAppRole.Factory,
|
||||
},
|
||||
LogicalBackends: map[string]logical.Factory{
|
||||
"kv": logicalKv.Factory,
|
||||
},
|
||||
},
|
||||
&vault.TestClusterOptions{
|
||||
NumCores: 1,
|
||||
HandlerFunc: func(properties *vault.HandlerProperties) http.Handler {
|
||||
h.props = properties
|
||||
h.t = t
|
||||
return &h
|
||||
},
|
||||
})
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
vault.TestWaitActive(t, cluster.Cores[0].Core)
|
||||
serverClient := cluster.Cores[0].Client
|
||||
|
||||
// Unset the environment variable so that agent picks up the right test
|
||||
// cluster address
|
||||
defer os.Setenv(api.EnvVaultAddress, os.Getenv(api.EnvVaultAddress))
|
||||
os.Unsetenv(api.EnvVaultAddress)
|
||||
|
||||
_, err := serverClient.Logical().Write("secret/foo", map[string]interface{}{
|
||||
"bar": "baz",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
intRef := func(i int) *int {
|
||||
return &i
|
||||
}
|
||||
// start test cases here
|
||||
testCases := map[string]struct {
|
||||
retries *int
|
||||
expectError bool
|
||||
}{
|
||||
"none": {
|
||||
retries: intRef(-1),
|
||||
expectError: true,
|
||||
},
|
||||
"one": {
|
||||
retries: intRef(1),
|
||||
expectError: true,
|
||||
},
|
||||
"two": {
|
||||
retries: intRef(2),
|
||||
expectError: false,
|
||||
},
|
||||
"missing": {
|
||||
retries: nil,
|
||||
expectError: false,
|
||||
},
|
||||
"default": {
|
||||
retries: intRef(0),
|
||||
expectError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for tcname, tc := range testCases {
|
||||
t.Run(tcname, func(t *testing.T) {
|
||||
h.failCount = 2
|
||||
|
||||
cacheConfig := fmt.Sprintf(`
|
||||
cache {
|
||||
}
|
||||
`)
|
||||
listenAddr := "127.0.0.1:18123"
|
||||
listenConfig := fmt.Sprintf(`
|
||||
listener "tcp" {
|
||||
address = "%s"
|
||||
tls_disable = true
|
||||
}
|
||||
`, listenAddr)
|
||||
|
||||
var retryConf string
|
||||
if tc.retries != nil {
|
||||
retryConf = fmt.Sprintf("retry { num_retries = %d }", *tc.retries)
|
||||
}
|
||||
|
||||
config := fmt.Sprintf(`
|
||||
vault {
|
||||
address = "%s"
|
||||
%s
|
||||
tls_skip_verify = true
|
||||
}
|
||||
%s
|
||||
%s
|
||||
`, serverClient.Address(), retryConf, cacheConfig, listenConfig)
|
||||
|
||||
configPath := makeTempFile(t, "config.hcl", config)
|
||||
defer os.Remove(configPath)
|
||||
|
||||
// Start the agent
|
||||
_, cmd := testAgentCommand(t, logger)
|
||||
cmd.startedCh = make(chan struct{})
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
cmd.Run([]string{"-config", configPath})
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-cmd.startedCh:
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Errorf("timeout")
|
||||
}
|
||||
|
||||
client, err := api.NewClient(api.DefaultConfig())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
client.SetToken(serverClient.Token())
|
||||
client.SetMaxRetries(0)
|
||||
err = client.SetAddress("http://" + listenAddr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
secret, err := client.Logical().Read("secret/foo")
|
||||
switch {
|
||||
case (err != nil || secret == nil) && tc.expectError:
|
||||
case (err == nil || secret != nil) && !tc.expectError:
|
||||
default:
|
||||
t.Fatalf("%s expectError=%v error=%v secret=%v", tcname, tc.expectError, err, secret)
|
||||
}
|
||||
if secret != nil && secret.Data["foo"] != nil {
|
||||
val := secret.Data["foo"].(map[string]interface{})
|
||||
if !reflect.DeepEqual(val, map[string]interface{}{"bar": "baz"}) {
|
||||
t.Fatalf("expected key 'foo' to yield bar=baz, got: %v", val)
|
||||
}
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
|
||||
close(cmd.ShutdownCh)
|
||||
wg.Wait()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue