Add configurable exponential backoff to Agent auto-auth (#10964)
This commit is contained in:
parent
0574f5aac7
commit
e60cc11f33
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -62,12 +62,14 @@ type AutoAuth struct {
|
||||||
|
|
||||||
// Method represents the configuration for the authentication backend
|
// 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"`
|
||||||
WrapTTLRaw interface{} `hcl:"wrap_ttl"`
|
WrapTTLRaw interface{} `hcl:"wrap_ttl"`
|
||||||
WrapTTL time.Duration `hcl:"-"`
|
WrapTTL time.Duration `hcl:"-"`
|
||||||
Namespace string `hcl:"namespace"`
|
MaxBackoffRaw interface{} `hcl:"max_backoff"`
|
||||||
Config map[string]interface{}
|
MaxBackoff time.Duration `hcl:"-"`
|
||||||
|
Namespace string `hcl:"namespace"`
|
||||||
|
Config map[string]interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sink defines a location to write the authenticated token
|
// Sink defines a location to write the authenticated token
|
||||||
|
@ -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{
|
||||||
{
|
{
|
||||||
|
@ -178,9 +179,10 @@ func TestLoadConfigFile_Method_Wrapping(t *testing.T) {
|
||||||
},
|
},
|
||||||
AutoAuth: &AutoAuth{
|
AutoAuth: &AutoAuth{
|
||||||
Method: &Method{
|
Method: &Method{
|
||||||
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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue