Skip to content

Commit

Permalink
configurable lease check wait for non-renewable secrets
Browse files Browse the repository at this point in the history
Now the vault config has a `lease_renewal_threshold` parameter which controls the
fraction of how long of the original lease duration consul-template should wait to
ask for a new secret on non-renewable secrets (like for PKIs). By default, consul-template
will wait (90 +/- 5)% of the lease time.
  • Loading branch information
rgeoghegan authored and eikenb committed Mar 8, 2022
1 parent ae2bbca commit cc67cc9
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 18 deletions.
5 changes: 5 additions & 0 deletions config/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ func FileModePresent(o *os.FileMode) bool {
return *o != 0
}

// Float64 returns a pointer to the given float64
func Float64(f float64) *float64 {
return &f
}

// Int returns a pointer to the given int.
func Int(i int) *int {
return &i
Expand Down
22 changes: 21 additions & 1 deletion config/vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const (

// DefaultVaultLeaseDuration is the default lease duration in seconds.
DefaultVaultLeaseDuration = 5 * time.Minute

// DefaultLeaseRenewalThreshold is the default fraction of a non-renewable
// lease to wait for before refreshing
DefaultLeaseRenewalThreshold = .90
)

// VaultConfig is the configuration for connecting to a vault server.
Expand Down Expand Up @@ -74,6 +78,11 @@ type VaultConfig struct {
// DefaultLeaseDuration configures the default lease duration when not explicitly
// set by vault
DefaultLeaseDuration *time.Duration `mapstructure:"default_lease_duration"`

// LeaseRenewalThreshold configues how long Consul Template should wait for to
// refresh dynamic, non-renewable leases, measured as a fraction of the lease
// duration.
LeaseRenewalThreshold *float64 `mapstructure:"lease_renewal_threshold"`
}

// DefaultVaultConfig returns a configuration that is populated with the
Expand Down Expand Up @@ -125,6 +134,7 @@ func (c *VaultConfig) Copy() *VaultConfig {
o.UnwrapToken = c.UnwrapToken

o.DefaultLeaseDuration = c.DefaultLeaseDuration
o.LeaseRenewalThreshold = c.LeaseRenewalThreshold

return &o
}
Expand Down Expand Up @@ -191,6 +201,10 @@ func (c *VaultConfig) Merge(o *VaultConfig) *VaultConfig {
r.DefaultLeaseDuration = o.DefaultLeaseDuration
}

if o.LeaseRenewalThreshold != nil {
r.LeaseRenewalThreshold = o.LeaseRenewalThreshold
}

return r
}

Expand Down Expand Up @@ -292,6 +306,10 @@ func (c *VaultConfig) Finalize() {
if c.DefaultLeaseDuration == nil {
c.DefaultLeaseDuration = TimeDuration(DefaultVaultLeaseDuration)
}

if c.LeaseRenewalThreshold == nil {
c.LeaseRenewalThreshold = Float64(DefaultLeaseRenewalThreshold)
}
}

// GoString defines the printable version of this struct.
Expand All @@ -310,8 +328,9 @@ func (c *VaultConfig) GoString() string {
"Token:%t, "+
"VaultAgentTokenFile:%t, "+
"Transport:%#v, "+
"UnwrapToken:%s"+
"UnwrapToken:%s, "+
"DefaultLeaseDuration:%s, "+
"LeaseRenewalThreshold:%f, "+
"}",
StringGoString(c.Address),
BoolGoString(c.Enabled),
Expand All @@ -324,5 +343,6 @@ func (c *VaultConfig) GoString() string {
c.Transport,
BoolGoString(c.UnwrapToken),
TimeDurationGoString(c.DefaultLeaseDuration),
*c.LeaseRenewalThreshold,
)
}
94 changes: 82 additions & 12 deletions config/vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ func TestVaultConfig_Copy(t *testing.T) {
Transport: &TransportConfig{
DialKeepAlive: TimeDuration(20 * time.Second),
},
UnwrapToken: Bool(true),
VaultAgentTokenFile: String("/tmp/vault/agent/token"),
DefaultLeaseDuration: TimeDuration(5 * time.Minute),
UnwrapToken: Bool(true),
VaultAgentTokenFile: String("/tmp/vault/agent/token"),
DefaultLeaseDuration: TimeDuration(5 * time.Minute),
LeaseRenewalThreshold: Float64(0.70),
},
},
}
Expand Down Expand Up @@ -323,6 +324,30 @@ func TestVaultConfig_Merge(t *testing.T) {
&VaultConfig{DefaultLeaseDuration: TimeDuration(5 * time.Minute)},
&VaultConfig{DefaultLeaseDuration: TimeDuration(5 * time.Minute)},
},
{
"lease_renewal_threshold_overrides",
&VaultConfig{LeaseRenewalThreshold: Float64(0.8)},
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
},
{
"lease_renewal_threshold_empty_one",
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
&VaultConfig{},
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
},
{
"lease_renewal_threshold_empty_two",
&VaultConfig{},
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
},
{
"lease_renewal_threshold_same",
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
&VaultConfig{LeaseRenewalThreshold: Float64(0.7)},
},
}

for i, tc := range cases {
Expand Down Expand Up @@ -375,8 +400,9 @@ func TestVaultConfig_Finalize(t *testing.T) {
MaxIdleConnsPerHost: Int(DefaultMaxIdleConnsPerHost),
TLSHandshakeTimeout: TimeDuration(DefaultTLSHandshakeTimeout),
},
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(DefaultVaultLeaseDuration),
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(DefaultVaultLeaseDuration),
LeaseRenewalThreshold: Float64(DefaultLeaseRenewalThreshold),
},
},
{
Expand Down Expand Up @@ -414,8 +440,9 @@ func TestVaultConfig_Finalize(t *testing.T) {
MaxIdleConnsPerHost: Int(DefaultMaxIdleConnsPerHost),
TLSHandshakeTimeout: TimeDuration(DefaultTLSHandshakeTimeout),
},
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(DefaultVaultLeaseDuration),
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(DefaultVaultLeaseDuration),
LeaseRenewalThreshold: Float64(DefaultLeaseRenewalThreshold),
},
},
{
Expand Down Expand Up @@ -453,14 +480,15 @@ func TestVaultConfig_Finalize(t *testing.T) {
MaxIdleConnsPerHost: Int(DefaultMaxIdleConnsPerHost),
TLSHandshakeTimeout: TimeDuration(DefaultTLSHandshakeTimeout),
},
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(DefaultVaultLeaseDuration),
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(DefaultVaultLeaseDuration),
LeaseRenewalThreshold: Float64(DefaultLeaseRenewalThreshold),
},
},
{
"with_default_lease_duration",
&VaultConfig{
Address: String("address"),
Address: String("address"),
DefaultLeaseDuration: TimeDuration(1 * time.Minute),
},
&VaultConfig{
Expand Down Expand Up @@ -493,8 +521,50 @@ func TestVaultConfig_Finalize(t *testing.T) {
MaxIdleConnsPerHost: Int(DefaultMaxIdleConnsPerHost),
TLSHandshakeTimeout: TimeDuration(DefaultTLSHandshakeTimeout),
},
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(1 * time.Minute),
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(1 * time.Minute),
LeaseRenewalThreshold: Float64(DefaultLeaseRenewalThreshold),
},
},
{
"with_lease_renewal_threshold",
&VaultConfig{
Address: String("address"),
LeaseRenewalThreshold: Float64(0.70),
},
&VaultConfig{
Address: String("address"),
Enabled: Bool(true),
Namespace: String(""),
RenewToken: Bool(false),
Retry: &RetryConfig{
Backoff: TimeDuration(DefaultRetryBackoff),
MaxBackoff: TimeDuration(DefaultRetryMaxBackoff),
Enabled: Bool(true),
Attempts: Int(DefaultRetryAttempts),
},
SSL: &SSLConfig{
CaCert: String(""),
CaPath: String(""),
Cert: String(""),
Enabled: Bool(true),
Key: String(""),
ServerName: String(""),
Verify: Bool(true),
},
Token: String(""),
Transport: &TransportConfig{
DialKeepAlive: TimeDuration(DefaultDialKeepAlive),
DialTimeout: TimeDuration(DefaultDialTimeout),
DisableKeepAlives: Bool(false),
IdleConnTimeout: TimeDuration(DefaultIdleConnTimeout),
MaxIdleConns: Int(DefaultMaxIdleConns),
MaxIdleConnsPerHost: Int(DefaultMaxIdleConnsPerHost),
TLSHandshakeTimeout: TimeDuration(DefaultTLSHandshakeTimeout),
},
UnwrapToken: Bool(DefaultVaultUnwrapToken),
DefaultLeaseDuration: TimeDuration(DefaultVaultLeaseDuration),
LeaseRenewalThreshold: Float64(0.70),
},
},
}
Expand Down
1 change: 1 addition & 0 deletions dependency/health_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ func TestHealthConnectServiceQuery_Fetch(t *testing.T) {
inst.Node, inst.NodeID = "", ""
inst.Checks = nil
inst.NodeTaggedAddresses = nil
inst.ServiceTaggedAddresses = nil

assert.Equal(t, tc.exp, act)
})
Expand Down
30 changes: 25 additions & 5 deletions dependency/vault_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ import (

var (
// VaultDefaultLeaseDuration is the default lease duration in seconds.
VaultDefaultLeaseDuration time.Duration
onceVaultDefaultLeaseDuration sync.Once
VaultDefaultLeaseDuration time.Duration
onceVaultDefaultLeaseDuration sync.Once
VaultLeaseRenewalThreshold float64
onceVaultLeaseRenewalThreshold sync.Once
)

// Secret is the structure returned for every secret within Vault.
Expand Down Expand Up @@ -166,9 +168,19 @@ func leaseCheckWait(s *Secret) time.Duration {
// If the secret doesn't have a rotation period, this is a non-renewable leased
// secret.
// For non-renewable leases set the renew duration to use much of the secret
// lease as possible. Use a stagger over 85%-95% of the lease duration so that
// many clients do not hit Vault simultaneously.
sleep = sleep * (.85 + rand.Float64()*0.1)
// lease as possible. Use a stagger over the configured threshold
// fraction of the lease duration so that many clients do not hit
// Vault simultaneously.
finalFraction := VaultLeaseRenewalThreshold + (rand.Float64()-0.5)*0.1
if finalFraction >= 1.0 || finalFraction <= 0.0 {
// If the fraction randomly winds up outside of (0.0-1.0), clamp
// back down to the VaultLeaseRenewalThreshold provided by the user,
// since a) the user picked that value, so they should be
// comfortable with it, and b) it should not skew the staggering too
// much
finalFraction = VaultLeaseRenewalThreshold
}
sleep = sleep * finalFraction
}

return time.Duration(sleep)
Expand Down Expand Up @@ -351,3 +363,11 @@ func SetVaultDefaultLeaseDuration(t time.Duration) {
}
onceVaultDefaultLeaseDuration.Do(set)
}

// Make sure to only set VaultLeaseRenewalThreshold once
func SetVaultLeaseRenewalThreshold(f float64) {
set := func() {
VaultLeaseRenewalThreshold = f
}
onceVaultLeaseRenewalThreshold.Do(set)
}
1 change: 1 addition & 0 deletions dependency/vault_common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

func init() {
VaultDefaultLeaseDuration = 0
VaultLeaseRenewalThreshold = .90
}

func TestVaultRenewDuration(t *testing.T) {
Expand Down
6 changes: 6 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,12 @@ vault {
# 5 minutes.
default_lease_duration = "60s"
# The fraction of the lease duration of a non-renewable secret Consul
# Template will wait for. This is used to calculate the sleep duration for
# rechecking a Vault secret value. This field is optional and will default to
# 90% of the lease time.
default_lease_duration = 0.90
# This option tells Consul Template to automatically renew the Vault token
# given. If you are unfamiliar with Vault's architecture, Vault requires
# tokens be renewed at some regular interval or they will be revoked. Consul
Expand Down
1 change: 1 addition & 0 deletions manager/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,7 @@ func (r *Runner) init() error {
log.Printf("[DEBUG] (runner) final config: %s", result)

dep.SetVaultDefaultLeaseDuration(config.TimeDurationVal(r.config.Vault.DefaultLeaseDuration))
dep.SetVaultLeaseRenewalThreshold(*r.config.Vault.LeaseRenewalThreshold)

// Create the clientset
clients, err := newClientSet(r.config)
Expand Down

0 comments on commit cc67cc9

Please sign in to comment.