Skip to content
This repository has been archived by the owner on Jul 12, 2023. It is now read-only.

Add more rotation tests #1759

Merged
merged 1 commit into from
Feb 3, 2021
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
68 changes: 64 additions & 4 deletions pkg/controller/rotation/handle_token_rotate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"time"

"github.com/google/exposure-notifications-server/pkg/keys"
"github.com/google/exposure-notifications-verification-server/internal/envstest"
"github.com/google/exposure-notifications-verification-server/internal/project"
"github.com/google/exposure-notifications-verification-server/pkg/config"
"github.com/google/exposure-notifications-verification-server/pkg/database"
Expand All @@ -32,8 +33,6 @@ func TestHandleRotate(t *testing.T) {

ctx := project.TestContext(t)

db, _ := testDatabaseInstance.NewDatabase(t, nil)

keyManager := keys.TestKeyManager(t)
keyManagerSigner, ok := keyManager.(keys.SigningKeyManager)
if !ok {
Expand All @@ -46,18 +45,21 @@ func TestHandleRotate(t *testing.T) {
t.Fatal(err)
}

config := &config.RotationConfig{
cfg := &config.RotationConfig{
TokenSigning: config.TokenSigningConfig{
TokenSigningKeys: []string{tokenSigningKey},
TokenSigningKeyIDs: []string{"v1"},
},
TokenSigningKeyMaxAge: 30 * time.Second,
}
c := New(config, db, keyManagerSigner, h)

t.Run("rotates", func(t *testing.T) {
t.Parallel()

db, _ := testDatabaseInstance.NewDatabase(t, nil)

c := New(cfg, db, keyManagerSigner, h)

// Rotating should create a new key since none exists.
{
r, err := http.NewRequest("GET", "/", nil)
Expand Down Expand Up @@ -135,4 +137,62 @@ func TestHandleRotate(t *testing.T) {
}
}
})

t.Run("too_early", func(t *testing.T) {
t.Parallel()

db, _ := testDatabaseInstance.NewDatabase(t, nil)

cfg := &config.RotationConfig{
TokenSigning: config.TokenSigningConfig{
TokenSigningKeys: []string{tokenSigningKey},
TokenSigningKeyIDs: []string{"v1"},
},
TokenSigningKeyMaxAge: 30 * time.Second,
MinTTL: 5 * time.Minute,
}

c := New(cfg, db, keyManagerSigner, h)

r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
r = r.Clone(ctx)

w := httptest.NewRecorder()

c.HandleRotate().ServeHTTP(w, r)
if got, want := w.Code, 200; got != want {
t.Errorf("expected %d to be %d", got, want)
}

// again
c.HandleRotate().ServeHTTP(w, r)
if got, want := w.Code, 200; got != want {
t.Errorf("expected %d to be %d", got, want)
}
})

t.Run("database_error", func(t *testing.T) {
t.Parallel()

db, _ := testDatabaseInstance.NewDatabase(t, nil)
db.SetRawDB(envstest.NewFailingDatabase())

c := New(cfg, db, keyManagerSigner, h)
r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
r = r.Clone(ctx)

w := httptest.NewRecorder()

c.HandleRotate().ServeHTTP(w, r)

if got, want := w.Code, 500; got != want {
t.Errorf("expected %d to be %d", got, want)
}
})
}
170 changes: 118 additions & 52 deletions pkg/controller/rotation/handle_verification_rotate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

"github.com/google/exposure-notifications-server/pkg/keys"
"github.com/google/exposure-notifications-verification-server/internal/envstest"
"github.com/google/exposure-notifications-verification-server/internal/project"
"github.com/google/exposure-notifications-verification-server/pkg/config"
"github.com/google/exposure-notifications-verification-server/pkg/database"
Expand All @@ -33,18 +34,6 @@ func TestHandleVerificationRotation(t *testing.T) {

ctx := project.TestContext(t)

db, _ := testDatabaseInstance.NewDatabase(t, nil)

realm := database.NewRealmWithDefaults("state")
realm.AutoRotateCertificateKey = true
realm.UseRealmCertificateKey = true
realm.CertificateIssuer = "iss"
realm.CertificateAudience = "aud"
realm.CertificateDuration = database.FromDuration(time.Second)
if err := db.SaveRealm(realm, database.SystemTest); err != nil {
t.Fatal(err)
}

keyManager := keys.TestKeyManager(t)
keyManagerSigner, ok := keyManager.(keys.SigningKeyManager)
if !ok {
Expand All @@ -56,61 +45,138 @@ func TestHandleVerificationRotation(t *testing.T) {
t.Fatal(err)
}

config := &config.RotationConfig{
VerificationSigningKeyMaxAge: 10 * time.Second,
VerificationActivationDelay: 2 * time.Second,
MinTTL: time.Microsecond,
}
c := New(config, db, keyManagerSigner, h)

// create the initial signing key version, which will make it active.
if _, err := realm.CreateSigningKeyVersion(ctx, db, database.SystemTest); err != nil {
t.Fatal(err)
}
// Initial state - 1 active signing key.
checkKeys(t, db, realm, 1, 0)

// Wait the max age, and run the test.
time.Sleep(config.VerificationSigningKeyMaxAge + time.Second)
invokeRotate(t, ctx, c)
// There should be 2 keys on the realm now, the older one should still be the active one.
checkKeys(t, db, realm, 2, 1)

// Wait long enough for the activation delay.
time.Sleep(config.VerificationActivationDelay + time.Second)
invokeRotate(t, ctx, c)
// There should still be 2 signing keys, but now the first one should be active.
checkKeys(t, db, realm, 2, 0)

// Wait long enough for original key to be deleted.
time.Sleep(config.VerificationActivationDelay + time.Second)
invokeRotate(t, ctx, c)
// Original key should be destroyed, only 1 key and it's active now.
checkKeys(t, db, realm, 1, 0)
t.Run("rotates", func(t *testing.T) {
t.Parallel()

db, _ := testDatabaseInstance.NewDatabase(t, nil)

realm := database.NewRealmWithDefaults("state")
realm.AutoRotateCertificateKey = true
realm.UseRealmCertificateKey = true
realm.CertificateIssuer = "iss"
realm.CertificateAudience = "aud"
realm.CertificateDuration = database.FromDuration(time.Second)
if err := db.SaveRealm(realm, database.SystemTest); err != nil {
t.Fatal(err)
}

cfg := &config.RotationConfig{
VerificationSigningKeyMaxAge: 10 * time.Second,
VerificationActivationDelay: 2 * time.Second,
MinTTL: time.Microsecond,
}
c := New(cfg, db, keyManagerSigner, h)

// create the initial signing key version, which will make it active.
if _, err := realm.CreateSigningKeyVersion(ctx, db, database.SystemTest); err != nil {
t.Fatal(err)
}
// Initial state - 1 active signing key.
checkKeys(t, db, realm, 1, 0)

// Wait the max age, and run the test.
time.Sleep(cfg.VerificationSigningKeyMaxAge + time.Second)
invokeRotate(t, ctx, c)
// There should be 2 keys on the realm now, the older one should still be the active one.
checkKeys(t, db, realm, 2, 1)

// Wait long enough for the activation delay.
time.Sleep(cfg.VerificationActivationDelay + time.Second)
invokeRotate(t, ctx, c)
// There should still be 2 signing keys, but now the first one should be active.
checkKeys(t, db, realm, 2, 0)

// Wait long enough for original key to be deleted.
time.Sleep(cfg.VerificationActivationDelay + time.Second)
invokeRotate(t, ctx, c)
// Original key should be destroyed, only 1 key and it's active now.
checkKeys(t, db, realm, 1, 0)
})

t.Run("too_early", func(t *testing.T) {
t.Parallel()

db, _ := testDatabaseInstance.NewDatabase(t, nil)

cfg := &config.RotationConfig{
VerificationSigningKeyMaxAge: 2 * time.Second,
VerificationActivationDelay: 1 * time.Second,
MinTTL: 5 * time.Minute,
}

c := New(cfg, db, keyManagerSigner, h)

r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
r = r.Clone(ctx)

w := httptest.NewRecorder()

c.HandleVerificationRotate().ServeHTTP(w, r)
if got, want := w.Code, 200; got != want {
t.Errorf("expected %d to be %d", got, want)
}

// again
c.HandleVerificationRotate().ServeHTTP(w, r)
if got, want := w.Code, 200; got != want {
t.Errorf("expected %d to be %d", got, want)
}
})

t.Run("database_error", func(t *testing.T) {
t.Parallel()

db, _ := testDatabaseInstance.NewDatabase(t, nil)
db.SetRawDB(envstest.NewFailingDatabase())

cfg := &config.RotationConfig{
VerificationSigningKeyMaxAge: 2 * time.Second,
VerificationActivationDelay: 1 * time.Second,
MinTTL: time.Microsecond,
}
c := New(cfg, db, keyManagerSigner, h)

r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
r = r.Clone(ctx)

w := httptest.NewRecorder()

c.HandleVerificationRotate().ServeHTTP(w, r)

if got, want := w.Code, 500; got != want {
t.Errorf("expected %d to be %d", got, want)
}
})
}

func checkKeys(t testing.TB, db *database.Database, realm *database.Realm, count, active int) {
t.Helper()
func checkKeys(tb testing.TB, db *database.Database, realm *database.Realm, count, active int) {
tb.Helper()

keys, err := realm.ListSigningKeys(db)
if err != nil {
t.Fatalf("listing signing keys: %v", err)
tb.Fatalf("listing signing keys: %v", err)
}

if l := len(keys); l != count {
t.Fatalf("expected key count wrong, want: %v got: %v", count, l)
tb.Fatalf("expected key count wrong, want: %v got: %v", count, l)
}
if !keys[active].Active {
t.Fatalf("expected active key (%v) is not active", active)
tb.Fatalf("expected active key (%v) is not active", active)
}
}

func invokeRotate(t testing.TB, ctx context.Context, c *Controller) {
t.Helper()
func invokeRotate(tb testing.TB, ctx context.Context, c *Controller) {
tb.Helper()

r, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
tb.Fatal(err)
}
r = r.Clone(ctx)

Expand All @@ -119,6 +185,6 @@ func invokeRotate(t testing.TB, ctx context.Context, c *Controller) {
c.HandleVerificationRotate().ServeHTTP(w, r)

if w.Code != http.StatusOK {
t.Fatalf("invoke didn't return success, status: %v", w.Code)
tb.Fatalf("invoke didn't return success, status: %v", w.Code)
}
}