mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-30 18:17:55 +00:00 
			
		
		
		
	Add configurable exponential backoff to Agent auto-auth (#10964)
This commit is contained in:
		
							
								
								
									
										5
									
								
								changelog/10964.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								changelog/10964.txt
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | ```release-note:changes | ||||||
|  | agent: Failed auto-auth attempts are now throttled by an exponential backoff instead of the | ||||||
|  | ~2 second retry delay. The maximum backoff may be configured with the new `max_backoff` parameter, | ||||||
|  | which defaults to 5 minutes. | ||||||
|  | ``` | ||||||
| @@ -575,6 +575,7 @@ func (c *AgentCommand) Run(args []string) int { | |||||||
| 			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, | ||||||
|  | 			MaxBackoff:                   config.AutoAuth.Method.MaxBackoff, | ||||||
| 			EnableReauthOnNewCredentials: config.AutoAuth.EnableReauthOnNewCredentials, | 			EnableReauthOnNewCredentials: config.AutoAuth.EnableReauthOnNewCredentials, | ||||||
| 			EnableTemplateTokenCh:        enableTokenCh, | 			EnableTemplateTokenCh:        enableTokenCh, | ||||||
| 		}) | 		}) | ||||||
|   | |||||||
| @@ -8,11 +8,16 @@ import ( | |||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	hclog "github.com/hashicorp/go-hclog" | 	"github.com/hashicorp/go-hclog" | ||||||
| 	"github.com/hashicorp/vault/api" | 	"github.com/hashicorp/vault/api" | ||||||
| 	"github.com/hashicorp/vault/sdk/helper/jsonutil" | 	"github.com/hashicorp/vault/sdk/helper/jsonutil" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | const ( | ||||||
|  | 	initialBackoff    = 1 * time.Second | ||||||
|  | 	defaultMaxBackoff = 5 * time.Minute | ||||||
|  | ) | ||||||
|  |  | ||||||
| // AuthMethod is the interface that auto-auth methods implement for the agent | // AuthMethod is the interface that auto-auth methods implement for the agent | ||||||
| // to use. | // to use. | ||||||
| type AuthMethod interface { | type AuthMethod interface { | ||||||
| @@ -48,6 +53,7 @@ type AuthHandler struct { | |||||||
| 	client                       *api.Client | 	client                       *api.Client | ||||||
| 	random                       *rand.Rand | 	random                       *rand.Rand | ||||||
| 	wrapTTL                      time.Duration | 	wrapTTL                      time.Duration | ||||||
|  | 	maxBackoff                   time.Duration | ||||||
| 	enableReauthOnNewCredentials bool | 	enableReauthOnNewCredentials bool | ||||||
| 	enableTemplateTokenCh        bool | 	enableTemplateTokenCh        bool | ||||||
| } | } | ||||||
| @@ -56,6 +62,7 @@ type AuthHandlerConfig struct { | |||||||
| 	Logger                       hclog.Logger | 	Logger                       hclog.Logger | ||||||
| 	Client                       *api.Client | 	Client                       *api.Client | ||||||
| 	WrapTTL                      time.Duration | 	WrapTTL                      time.Duration | ||||||
|  | 	MaxBackoff                   time.Duration | ||||||
| 	Token                        string | 	Token                        string | ||||||
| 	EnableReauthOnNewCredentials bool | 	EnableReauthOnNewCredentials bool | ||||||
| 	EnableTemplateTokenCh        bool | 	EnableTemplateTokenCh        bool | ||||||
| @@ -72,6 +79,7 @@ func NewAuthHandler(conf *AuthHandlerConfig) *AuthHandler { | |||||||
| 		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, | ||||||
|  | 		maxBackoff:                   conf.MaxBackoff, | ||||||
| 		enableReauthOnNewCredentials: conf.EnableReauthOnNewCredentials, | 		enableReauthOnNewCredentials: conf.EnableReauthOnNewCredentials, | ||||||
| 		enableTemplateTokenCh:        conf.EnableTemplateTokenCh, | 		enableTemplateTokenCh:        conf.EnableTemplateTokenCh, | ||||||
| 	} | 	} | ||||||
| @@ -91,6 +99,13 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) error { | |||||||
| 		return errors.New("auth handler: nil auth method") | 		return errors.New("auth handler: nil auth method") | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	backoff := initialBackoff | ||||||
|  | 	maxBackoff := defaultMaxBackoff | ||||||
|  |  | ||||||
|  | 	if ah.maxBackoff > 0 { | ||||||
|  | 		maxBackoff = ah.maxBackoff | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	ah.logger.Info("starting auth handler") | 	ah.logger.Info("starting auth handler") | ||||||
| 	defer func() { | 	defer func() { | ||||||
| 		am.Shutdown() | 		am.Shutdown() | ||||||
| @@ -130,8 +145,7 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) error { | |||||||
| 		default: | 		default: | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// Create a fresh backoff value | 		backoff = calculateBackoff(backoff, maxBackoff) | ||||||
| 		backoff := 2*time.Second + time.Duration(ah.random.Int63()%int64(time.Second*2)-int64(time.Second)) |  | ||||||
|  |  | ||||||
| 		var clientToUse *api.Client | 		var clientToUse *api.Client | ||||||
| 		var err error | 		var err error | ||||||
| @@ -311,3 +325,16 @@ func (ah *AuthHandler) Run(ctx context.Context, am AuthMethod) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // calculateBackoff determines a new backoff duration that is roughly twice | ||||||
|  | // the previous value, capped to a max value, with a measure of randomness. | ||||||
|  | func calculateBackoff(previous, max time.Duration) time.Duration { | ||||||
|  | 	maxBackoff := 2 * previous | ||||||
|  | 	if maxBackoff > max { | ||||||
|  | 		maxBackoff = max | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Trim a random amount (0-25%) off the doubled duration | ||||||
|  | 	trim := rand.Int63n(int64(maxBackoff) / 4) | ||||||
|  | 	return maxBackoff - time.Duration(trim) | ||||||
|  | } | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	hclog "github.com/hashicorp/go-hclog" | 	"github.com/hashicorp/go-hclog" | ||||||
| 	"github.com/hashicorp/vault/api" | 	"github.com/hashicorp/vault/api" | ||||||
| 	"github.com/hashicorp/vault/builtin/credential/userpass" | 	"github.com/hashicorp/vault/builtin/credential/userpass" | ||||||
| 	vaulthttp "github.com/hashicorp/vault/http" | 	vaulthttp "github.com/hashicorp/vault/http" | ||||||
| @@ -106,3 +106,42 @@ consumption: | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | func TestCalculateBackoff(t *testing.T) { | ||||||
|  | 	tests := []struct { | ||||||
|  | 		previous time.Duration | ||||||
|  | 		max      time.Duration | ||||||
|  | 		expMin   time.Duration | ||||||
|  | 		expMax   time.Duration | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			1000 * time.Millisecond, | ||||||
|  | 			60000 * time.Millisecond, | ||||||
|  | 			1500 * time.Millisecond, | ||||||
|  | 			2000 * time.Millisecond, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			1000 * time.Millisecond, | ||||||
|  | 			5000 * time.Millisecond, | ||||||
|  | 			1500 * time.Millisecond, | ||||||
|  | 			2000 * time.Millisecond, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			4000 * time.Millisecond, | ||||||
|  | 			5000 * time.Millisecond, | ||||||
|  | 			3750 * time.Millisecond, | ||||||
|  | 			5000 * time.Millisecond, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for _, test := range tests { | ||||||
|  | 		for i := 0; i < 100; i++ { | ||||||
|  | 			backoff := calculateBackoff(test.previous, test.max) | ||||||
|  |  | ||||||
|  | 			// Verify that the new backoff is 75-100% of 2*previous, but <= than the max | ||||||
|  | 			if backoff < test.expMin || backoff > test.expMax { | ||||||
|  | 				t.Fatalf("expected backoff in range %v to %v, got: %v", test.expMin, test.expMax, backoff) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|   | |||||||
| @@ -66,6 +66,8 @@ type Method struct { | |||||||
| 	MountPath     string        `hcl:"mount_path"` | 	MountPath     string        `hcl:"mount_path"` | ||||||
| 	WrapTTLRaw    interface{}   `hcl:"wrap_ttl"` | 	WrapTTLRaw    interface{}   `hcl:"wrap_ttl"` | ||||||
| 	WrapTTL       time.Duration `hcl:"-"` | 	WrapTTL       time.Duration `hcl:"-"` | ||||||
|  | 	MaxBackoffRaw interface{}   `hcl:"max_backoff"` | ||||||
|  | 	MaxBackoff    time.Duration `hcl:"-"` | ||||||
| 	Namespace     string        `hcl:"namespace"` | 	Namespace     string        `hcl:"namespace"` | ||||||
| 	Config        map[string]interface{} | 	Config        map[string]interface{} | ||||||
| } | } | ||||||
| @@ -358,6 +360,14 @@ func parseAutoAuth(result *Config, list *ast.ObjectList) error { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if result.AutoAuth.Method.MaxBackoffRaw != nil { | ||||||
|  | 		var err error | ||||||
|  | 		if result.AutoAuth.Method.MaxBackoff, err = parseutil.ParseDurationSecond(result.AutoAuth.Method.MaxBackoffRaw); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 		result.AutoAuth.Method.MaxBackoffRaw = nil | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -126,6 +126,7 @@ func TestLoadConfigFile(t *testing.T) { | |||||||
| 				Config: map[string]interface{}{ | 				Config: map[string]interface{}{ | ||||||
| 					"role": "foobar", | 					"role": "foobar", | ||||||
| 				}, | 				}, | ||||||
|  | 				MaxBackoff: 0, | ||||||
| 			}, | 			}, | ||||||
| 			Sinks: []*Sink{ | 			Sinks: []*Sink{ | ||||||
| 				{ | 				{ | ||||||
| @@ -181,6 +182,7 @@ func TestLoadConfigFile_Method_Wrapping(t *testing.T) { | |||||||
| 				Type:       "aws", | 				Type:       "aws", | ||||||
| 				MountPath:  "auth/aws", | 				MountPath:  "auth/aws", | ||||||
| 				WrapTTL:    5 * time.Minute, | 				WrapTTL:    5 * time.Minute, | ||||||
|  | 				MaxBackoff: 2 * time.Minute, | ||||||
| 				Config: map[string]interface{}{ | 				Config: map[string]interface{}{ | ||||||
| 					"role": "foobar", | 					"role": "foobar", | ||||||
| 				}, | 				}, | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ auto_auth { | |||||||
| 		config = { | 		config = { | ||||||
| 			role = "foobar" | 			role = "foobar" | ||||||
| 		} | 		} | ||||||
|  | 		max_backoff = "2m" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	sink { | 	sink { | ||||||
|   | |||||||
| @@ -20,9 +20,8 @@ are locations where the agent should write a token any time the current token | |||||||
| value has changed. | value has changed. | ||||||
|  |  | ||||||
| When the agent is started with Auto-Auth enabled, it will attempt to acquire a | When the agent is started with Auto-Auth enabled, it will attempt to acquire a | ||||||
| Vault token using the configured Method. On failure, it will back off for a | Vault token using the configured Method. On failure, it will exponentially back | ||||||
| short while (including some randomness to help prevent thundering herd | off and then retry. On success, unless the auth method is configured to wrap | ||||||
| scenarios) and retry. On success, unless the auth method is configured to wrap |  | ||||||
| the tokens, it will keep the resulting token renewed until renewal is no longer | the tokens, it will keep the resulting token renewed until renewal is no longer | ||||||
| allowed or fails, at which point it will attempt to reauthenticate. | allowed or fails, at which point it will attempt to reauthenticate. | ||||||
|  |  | ||||||
| @@ -128,6 +127,10 @@ These are common configuration values that live within the `method` block: | |||||||
|   structure. Values can be an integer number of seconds or a stringish value |   structure. Values can be an integer number of seconds or a stringish value | ||||||
|   like `5m`. |   like `5m`. | ||||||
|  |  | ||||||
|  | - `max_backoff` `(string or integer: "5m")` - The maximum time Agent will delay | ||||||
|  |   before retrying after a failed auth attempt. The backoff will start at 1 second | ||||||
|  |   and double (with some randomness) after successive failures, capped by `max_backoff.` | ||||||
|  |  | ||||||
| - `config` `(object: required)` - Configuration of the method itself. See the | - `config` `(object: required)` - Configuration of the method itself. See the | ||||||
|   sidebar for information about each method. |   sidebar for information about each method. | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Jim Kalafut
					Jim Kalafut