From 3855ada98f41acbf8656984283750d652a88d5c4 Mon Sep 17 00:00:00 2001 From: Seth Vargo Date: Tue, 2 Feb 2021 20:21:36 -0500 Subject: [PATCH] Add more rotation tests (#1759) --- .../rotation/handle_token_rotate_test.go | 68 ++++++- .../handle_verification_rotate_test.go | 170 ++++++++++++------ 2 files changed, 182 insertions(+), 56 deletions(-) diff --git a/pkg/controller/rotation/handle_token_rotate_test.go b/pkg/controller/rotation/handle_token_rotate_test.go index 23a80475b..6668d82e3 100644 --- a/pkg/controller/rotation/handle_token_rotate_test.go +++ b/pkg/controller/rotation/handle_token_rotate_test.go @@ -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" @@ -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 { @@ -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) @@ -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) + } + }) } diff --git a/pkg/controller/rotation/handle_verification_rotate_test.go b/pkg/controller/rotation/handle_verification_rotate_test.go index 200d07eb9..ab31e49de 100644 --- a/pkg/controller/rotation/handle_verification_rotate_test.go +++ b/pkg/controller/rotation/handle_verification_rotate_test.go @@ -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" @@ -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 { @@ -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) @@ -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) } }