Skip to content

Commit

Permalink
[spire-agent] Added a jitter in spire agent svid renewal (#4534)
Browse files Browse the repository at this point in the history
Signed-off-by: stevend-uber <[email protected]>
  • Loading branch information
stevend-uber authored Oct 20, 2023
1 parent 52ad6ef commit b007648
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 3 deletions.
4 changes: 2 additions & 2 deletions pkg/agent/manager/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ func TestSVIDRotation(t *testing.T) {
// Now advance time enough that the cert is expiring soon enough that the
// manager will attempt to rotate, but be unable to since the read lock is
// held.
clk.Add(baseTTLSeconds / 2)
clk.Add(baseTTLSeconds)

closer := runManager(t, m)
defer closer()
Expand Down Expand Up @@ -1292,7 +1292,7 @@ func TestFetchJWTSVID(t *testing.T) {
require.Equal(t, expiresAtA, svid.ExpiresAt.Unix())

// expire the cached JWT soon and make sure new JWT is fetched
clk.Add(time.Second * 30)
clk.Add(time.Second * 45)
now = clk.Now()
tokenC := "C"
issuedAtC := now.Unix()
Expand Down
28 changes: 27 additions & 1 deletion pkg/common/rotationutil/rotationutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package rotationutil

import (
"crypto/x509"
"math/rand"
"time"

"github.com/spiffe/spire/pkg/agent/client"
Expand Down Expand Up @@ -38,6 +39,31 @@ func JWTSVIDExpired(svid *client.JWTSVID, now time.Time) bool {

func shouldRotate(now, beginTime, expiryTime time.Time) bool {
ttl := expiryTime.Sub(now)
// return true quickly if the expiry is already met.
if ttl <= 0 {
return true
}

halfLife := halfLife(beginTime, expiryTime)

// calculate a jitter delta to spread out rotations
delta := jitterDelta(halfLife)
min := halfLife - delta

jitteredHalfLife := time.Duration(rand.Int63n(int64(delta)*2) + int64(min)) //nolint // gosec: no need for cryptographic randomness here

return ttl <= jitteredHalfLife
}

// jitterDelta is a calculated delta centered to the half-life of the SVID.
// It's to spread out the renewal of SVID rotations to avoid spiky renewal requests.
// The jitter is calculated as ± 10% of the half-life of the SVID.
func jitterDelta(halfLife time.Duration) time.Duration {
// ± 10% of the half-life
return halfLife / 10
}

func halfLife(beginTime, expiryTime time.Time) time.Duration {
lifetime := expiryTime.Sub(beginTime)
return ttl <= lifetime/2
return lifetime / 2
}
8 changes: 8 additions & 0 deletions pkg/common/rotationutil/rotationutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ func TestShouldRotateX509(t *testing.T) {
require.NoError(t, err)

assert.True(t, ShouldRotateX509(mockClk.Now(), badCert))

// Cert that's already expired
temp.NotBefore = mockClk.Now().Add(-1 * time.Hour)
temp.NotAfter = mockClk.Now().Add(-1 * time.Minute)
badCert, _, err = util.SelfSign(temp)
require.NoError(t, err)

assert.True(t, ShouldRotateX509(mockClk.Now(), badCert))
}

func TestX509Expired(t *testing.T) {
Expand Down

0 comments on commit b007648

Please sign in to comment.