Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use lifespan instead of duration for calculating when cert should be rotate #1865

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions dependency/vault_pki.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,26 +118,26 @@
if cert == nil {
return 0, false
}
// These are all int64's with Seconds since the Epoch, handy for the math
start, end := cert.NotBefore.Unix(), cert.NotAfter.Unix()
now := time.Now().UTC().Unix()
if end <= now { // already expired
start, end := cert.NotBefore.UTC(), cert.NotAfter.UTC()
now := time.Now().UTC()
if end.Before(now) || end.Equal(now) { // already expired
return 0, false
}
lifespan := end - start // full ttl of cert
duration := end - now // duration remaining
gooddur := (duration * 9) / 10 // 90% of duration
mindur := (lifespan / 10) // 10% of lifespan
if gooddur <= mindur {
return 0, false // almost expired, get a new one
}
if gooddur > 100 { // 100 seconds
// add jitter if big enough for it to matter
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// between 87% and 93%
gooddur = gooddur + ((gooddur / 100) * int64(r.Intn(6)-3))

lifespanDur := end.Sub(start)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
lifespanMilliseconds := lifespanDur.Milliseconds()
// calculate the 'time the certificate should be rotated' by figuring out
// 87-93% of the lifespan and adding it to the start
rotationTime := start.Add(time.Millisecond * time.Duration(((lifespanMilliseconds*9)/10)+(lifespanMilliseconds*int64(r.Intn(6)-3))/100))

// after we have the 'time the certificate should be rotated', figure out how
// far it is from now to sleep
sleepFor := time.Duration(rotationTime.Sub(now))

Check failure on line 136 in dependency/vault_pki.go

View workflow job for this annotation

GitHub Actions / Run linters

unnecessary conversion (unconvert)
if sleepFor <= 0 {
return 0, false
}
sleepFor := time.Duration(gooddur * 1e9) // basically: gooddur*time.Second

return sleepFor, true
}

Expand Down
57 changes: 55 additions & 2 deletions dependency/vault_pki_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ package dependency

import (
"bytes"
"crypto/x509"
"crypto/x509/pkix"
"errors"
"fmt"
"os"
"strings"
"testing"
"time"

"github.com/hashicorp/consul-template/renderer"
"github.com/hashicorp/vault/api"
Expand Down Expand Up @@ -53,6 +57,46 @@ func Test_VaultPKI_notGoodFor(t *testing.T) {
}
}

func Test_VaulkPKI_goodFor(t *testing.T) {
tests := map[string]struct {
CertificateTTL time.Duration
}{
"one minute": {CertificateTTL: time.Minute},
"one hour": {CertificateTTL: time.Hour},
"one day": {CertificateTTL: time.Hour * 24},
"one week": {CertificateTTL: time.Hour * 24 * 7},
}
for name, tc := range tests {
NotBefore := time.Now()
NotAfter := time.Now().Add(tc.CertificateTTL)
certificate := x509.Certificate{
Subject: pkix.Name{
Organization: []string{"Acme Co"},
},
NotBefore: NotBefore,
NotAfter: NotAfter,
}

dur, ok := goodFor(&certificate)
if ok == false {
t.Errorf("%v: should be true", name)
}

ratio := dur.Seconds() / (NotAfter.Sub(NotBefore).Seconds())
// allow for a .01 epsilon for floating point comparison to prevent flakey tests
if ratio < .86 || ratio > .94 {
fmt.Println(ratio)
t.Errorf(
"%v: should be between 87 and 93, but was %.2f. NotBefore: %s, NotAfter: %s",
name,
ratio,
NotBefore,
NotAfter,
)
}
}
}

func Test_VaultPKI_pemsCert(t *testing.T) {
// tests w/ valid pems, and having it hidden behind various things
want := strings.TrimRight(strings.TrimSpace(validCert), "\n")
Expand Down Expand Up @@ -136,10 +180,16 @@ func Test_VaultPKI_refetch(t *testing.T) {
defer os.Remove(f.Name())

clients := testClients
TTL := "2s"
ttlDuration, err := time.ParseDuration(TTL)
if err != nil {
t.Fatal(err)
}

/// above is prep work
data := map[string]interface{}{
"common_name": "foo.example.com",
"ttl": "3s",
"ttl": TTL,
"ip_sans": "127.0.0.1,192.168.2.2",
}
d, err := NewVaultPKIQuery("pki/issue/example-dot-com", f.Name(), data)
Expand Down Expand Up @@ -189,7 +239,10 @@ func Test_VaultPKI_refetch(t *testing.T) {
t.Errorf("pemss don't match and should.")
}

// Don't pre-drain here as we want it to get a new pems
// forcefully wait the longest the certificate could be good force to ensure
// goodFor will always return needs renewal
<-d.sleepCh
time.Sleep(time.Millisecond * time.Duration(((ttlDuration.Milliseconds()*9)/10)+(ttlDuration.Milliseconds()*int64(3)/100)))
act3, rm, err := d.Fetch(clients, nil)
if err != nil {
t.Fatal(err)
Expand Down
Loading