Skip to content

Commit

Permalink
Merge pull request #219 from bitnami-labs/time2
Browse files Browse the repository at this point in the history
Honour creation time during rotation
  • Loading branch information
Marko Mikulicic authored Aug 7, 2019
2 parents da72d75 + 0011fc9 commit 543ce88
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 8 deletions.
15 changes: 14 additions & 1 deletion cmd/controller/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,19 @@ func readKey(secret v1.Secret) (*rsa.PrivateKey, []*x509.Certificate, error) {
}
}

func writeKey(client kubernetes.Interface, key *rsa.PrivateKey, certs []*x509.Certificate, namespace, label, prefix string) (string, error) {
type writeKeyOpt func(*writeKeyOpts)
type writeKeyOpts struct{ creationTime metav1.Time }

func writeKeyWithCreationTime(t metav1.Time) writeKeyOpt {
return func(opts *writeKeyOpts) { opts.creationTime = t }
}

func writeKey(client kubernetes.Interface, key *rsa.PrivateKey, certs []*x509.Certificate, namespace, label, prefix string, optSetters ...writeKeyOpt) (string, error) {
var opts writeKeyOpts
for _, o := range optSetters {
o(&opts)
}

certbytes := []byte{}
for _, cert := range certs {
certbytes = append(certbytes, pem.EncodeToMemory(&pem.Block{Type: certUtil.CertificateBlockType, Bytes: cert.Raw})...)
Expand All @@ -66,6 +78,7 @@ func writeKey(client kubernetes.Interface, key *rsa.PrivateKey, certs []*x509.Ce
Labels: map[string]string{
label: "active",
},
CreationTimestamp: opts.creationTime,
},
Data: map[string][]byte{
v1.TLSPrivateKeyKey: pem.EncodeToMemory(&pem.Block{Type: keyutil.RSAPrivateKeyBlockType, Bytes: x509.MarshalPKCS1PrivateKey(key)}),
Expand Down
18 changes: 11 additions & 7 deletions cmd/controller/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,8 @@ func myNamespace() string {
// A period of 0 disables automatic rotation, but manual rotation (e.g. triggered by SIGUSR1)
// is still honoured.
func initKeyRotation(registry *KeyRegistry, period time.Duration) (func(), error) {
// Create a new key only if it's the first key or if we have automatic key rotation.
// Since the rotation period might be longer than the average pod run time (eviction, updates, crashes etc)
// we err on the side of increased rotation frequency rather than overshooting the rotation goals.
//
// TODO(mkm): implement rotation cadence based on resource times rather than just an in-process timer.
if period != 0 || len(registry.keys) == 0 {
// Create a new key only if it's the first key.
if len(registry.keys) == 0 {
if _, err := registry.generateKey(); err != nil {
return nil, err
}
Expand All @@ -136,7 +132,15 @@ func initKeyRotation(registry *KeyRegistry, period time.Duration) (func(), error
if period == 0 {
return keyGenFunc, nil
}
return ScheduleJobWithTrigger(period, period, keyGenFunc), nil

// If key rotation is enabled, we'll rotate the key when the most recent
// key becomes stale (older than period).
mostRecentKeyAge := time.Since(registry.mostRecentKey.creationTime)
initialDelay := period - mostRecentKeyAge
if initialDelay < 0 {
initialDelay = 0
}
return ScheduleJobWithTrigger(initialDelay, period, keyGenFunc), nil
}

func initKeyGenSignalListener(trigger func()) {
Expand Down
54 changes: 54 additions & 0 deletions cmd/controller/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,60 @@ func TestReuseKey(t *testing.T) {
}
}

func TestRotateStaleKey(t *testing.T) {
rand := testRand()
key, err := rsa.GenerateKey(rand, 512)
if err != nil {
t.Fatalf("Failed to generate test key: %v", err)
}

cert, err := signKey(rand, key)
if err != nil {
t.Fatalf("signKey failed: %v", err)
}

// we'll simulate the existence of a secret that is about to expire
// by making it old enough so that it's just "staleness" short of using
// the full rotation "period".
const (
period = 20 * time.Second
staleness = 100 * time.Millisecond
oldAge = period - staleness
)
client := fake.NewSimpleClientset()
_, err = writeKey(client, key, []*x509.Certificate{cert}, "namespace", SealedSecretsKeyLabel, "prefix",
writeKeyWithCreationTime(metav1.NewTime(time.Now().Add(-oldAge))))
if err != nil {
t.Errorf("writeKey() failed with: %v", err)
}

registry, err := initKeyRegistry(client, rand, "namespace", "prefix", SealedSecretsKeyLabel, 1024)
if err != nil {
t.Fatalf("initKeyRegistry() returned err: %v", err)
}

_, err = initKeyRotation(registry, period)
if err != nil {
t.Fatalf("initKeyRotation() returned err: %v", err)
}

client.ClearActions()

maxWait := 1 * time.Second
endTime := time.Now().Add(maxWait)
successful := false
for time.Now().Before(endTime) {
time.Sleep(50 * time.Millisecond)
if hasAction(client, "create", "secrets") {
successful = true
break
}
}
if !successful {
t.Errorf("trigger function failed to activate early key generation")
}
}

func writeLegacyKey(client kubernetes.Interface, key *rsa.PrivateKey, certs []*x509.Certificate, namespace, name string) (string, error) {
certbytes := []byte{}
for _, cert := range certs {
Expand Down

0 comments on commit 543ce88

Please sign in to comment.