diff --git a/builtin/logical/totp/backend_test.go b/builtin/logical/totp/backend_test.go index bccd4c28b4..fb1bd9bdaf 100644 --- a/builtin/logical/totp/backend_test.go +++ b/builtin/logical/totp/backend_test.go @@ -15,10 +15,12 @@ import ( "github.com/hashicorp/vault/helper/namespace" logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical" + "github.com/hashicorp/vault/sdk/helper/testhelpers/observations" "github.com/hashicorp/vault/sdk/logical" "github.com/mitchellh/mapstructure" otplib "github.com/pquerna/otp" totplib "github.com/pquerna/otp/totp" + "github.com/stretchr/testify/require" ) func createKey() (string, error) { @@ -45,6 +47,8 @@ func generateCode(key string, period uint, digits otplib.Digits, algorithm otpli func TestBackend_KeyName(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -84,7 +88,7 @@ func TestBackend_KeyName(t *testing.T) { } var resp *logical.Response for _, tc := range tests { - resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{ + req := &logical.Request{ Path: "keys/" + tc.KeyName, Operation: logical.UpdateOperation, Storage: config.StorageView, @@ -93,7 +97,8 @@ func TestBackend_KeyName(t *testing.T) { "account_name": "vault", "issuer": "hashicorp", }, - }) + } + resp, err = b.HandleRequest(namespace.RootContext(nil), req) if tc.Fail { if err == nil { t.Fatalf("expected an error for test %q", tc.Name) @@ -102,22 +107,62 @@ func TestBackend_KeyName(t *testing.T) { } else if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("bad: test name: %q\nresp: %#v\nerr: %v", tc.Name, resp, err) } - resp, err = b.HandleRequest(namespace.RootContext(nil), &logical.Request{ + lastObservation := obsRecorder.LastObservationOfType(ObservationTypeTOTPKeyCreate) + if lastObservation == nil { + t.Fatal("missing observation") + } + obsRequestID, ok := lastObservation.Data["request_id"] + if !ok { + t.Fatal("observation is missing request_id") + } + if obsRequestID != req.ID { + t.Fatalf("observation request_id %q does not equal req.ID %q", obsRequestID, req.ID) + } + obsKeyName, ok := lastObservation.Data["key_name"] + if !ok { + t.Fatal("observation is missing key_name") + } + if obsKeyName != tc.KeyName { + t.Fatalf("observation key_name %q != test KeyName %q", obsKeyName, tc.KeyName) + } + + req = &logical.Request{ Path: "code/" + tc.KeyName, Operation: logical.ReadOperation, Storage: config.StorageView, - }) + } + resp, err = b.HandleRequest(namespace.RootContext(nil), req) if err != nil || (resp != nil && resp.IsError()) { t.Fatalf("bad: test name: %q\nresp: %#v\nerr: %v", tc.Name, resp, err) } if resp.Data["code"].(string) == "" { t.Fatalf("failed to generate code for test %q", tc.Name) } + lastObservation = obsRecorder.LastObservationOfType(ObservationTypeTOTPCodeGenerate) + if lastObservation == nil { + t.Fatal("missing observation") + } + obsRequestID, ok = lastObservation.Data["request_id"] + if !ok { + t.Fatal("observation is missing request_id") + } + if obsRequestID != req.ID { + t.Fatalf("observation request_id %q does not equal req.ID %q", obsRequestID, req.ID) + } + obsKeyName, ok = lastObservation.Data["key_name"] + if !ok { + t.Fatal("observation is missing key_name") + } + if obsKeyName != tc.KeyName { + t.Fatalf("observation key_name %q != test KeyName %q", obsKeyName, tc.KeyName) + } } } func TestBackend_readCredentialsDefaultValues(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -144,15 +189,17 @@ func TestBackend_readCredentialsDefaultValues(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_readCredentialsEightDigitsThirtySecondPeriod(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -182,15 +229,17 @@ func TestBackend_readCredentialsEightDigitsThirtySecondPeriod(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_readCredentialsSixDigitsNinetySecondPeriod(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -220,15 +269,17 @@ func TestBackend_readCredentialsSixDigitsNinetySecondPeriod(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_readCredentialsSHA256(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -258,15 +309,17 @@ func TestBackend_readCredentialsSHA256(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_readCredentialsSHA512(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -296,15 +349,17 @@ func TestBackend_readCredentialsSHA512(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_keyCrudDefaultValues(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -335,22 +390,24 @@ func TestBackend_keyCrudDefaultValues(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepValidateCode(t, "test", code, true, false), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepValidateCode(t, "test", code, true, false, obsRecorder), // Next step should fail because it should be in the used cache - testAccStepValidateCode(t, "test", code+" ", false, true), - testAccStepValidateCode(t, "test", " "+code, false, true), - testAccStepValidateCode(t, "test", code, false, true), - testAccStepValidateCode(t, "test", invalidCode, false, false), - testAccStepDeleteKey(t, "test"), - testAccStepReadKey(t, "test", nil), + testAccStepValidateCode(t, "test", code+" ", false, true, obsRecorder), + testAccStepValidateCode(t, "test", " "+code, false, true, obsRecorder), + testAccStepValidateCode(t, "test", code, false, true, obsRecorder), + testAccStepValidateCode(t, "test", invalidCode, false, false, obsRecorder), + testAccStepDeleteKey(t, "test", obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_createKeyMissingKeyValue(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -366,14 +423,16 @@ func TestBackend_createKeyMissingKeyValue(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_createKeyInvalidKeyValue(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -390,14 +449,16 @@ func TestBackend_createKeyInvalidKeyValue(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_createKeyInvalidAlgorithm(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -418,14 +479,16 @@ func TestBackend_createKeyInvalidAlgorithm(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_createKeyInvalidPeriod(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -446,14 +509,16 @@ func TestBackend_createKeyInvalidPeriod(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_createKeyInvalidDigits(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -474,14 +539,16 @@ func TestBackend_createKeyInvalidDigits(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_generatedKeyDefaultValues(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -508,14 +575,16 @@ func TestBackend_generatedKeyDefaultValues(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), }, }) } func TestBackend_generatedKeyDefaultValuesNoQR(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -534,13 +603,15 @@ func TestBackend_generatedKeyDefaultValuesNoQR(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), }, }) } func TestBackend_generatedKeyNonDefaultKeySize(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -567,14 +638,16 @@ func TestBackend_generatedKeyNonDefaultKeySize(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), }, }) } func TestBackend_urlPassedNonGeneratedKeyInvalidPeriod(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -591,14 +664,16 @@ func TestBackend_urlPassedNonGeneratedKeyInvalidPeriod(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_urlPassedNonGeneratedKeyInvalidDigits(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -615,14 +690,16 @@ func TestBackend_urlPassedNonGeneratedKeyInvalidDigits(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_urlPassedNonGeneratedKeyIssuerInFirstPosition(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -648,15 +725,17 @@ func TestBackend_urlPassedNonGeneratedKeyIssuerInFirstPosition(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_urlPassedNonGeneratedKeyIssuerInQueryString(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -682,15 +761,17 @@ func TestBackend_urlPassedNonGeneratedKeyIssuerInQueryString(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_urlPassedNonGeneratedKeyMissingIssuer(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -716,15 +797,17 @@ func TestBackend_urlPassedNonGeneratedKeyMissingIssuer(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_urlPassedNonGeneratedKeyMissingAccountName(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -750,9 +833,9 @@ func TestBackend_urlPassedNonGeneratedKeyMissingAccountName(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } @@ -760,6 +843,8 @@ func TestBackend_urlPassedNonGeneratedKeyMissingAccountName(t *testing.T) { func TestBackend_urlPassedNonGeneratedKeyMissingAccountNameandIssuer(t *testing.T) { config := logical.TestBackendConfig() config.StorageView = &logical.InmemStorage{} + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder b, err := Factory(context.Background(), config) if err != nil { t.Fatal(err) @@ -784,15 +869,17 @@ func TestBackend_urlPassedNonGeneratedKeyMissingAccountNameandIssuer(t *testing. logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_urlPassedNonGeneratedKeyMissingAccountNameandIssuerandPadding(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -818,15 +905,17 @@ func TestBackend_urlPassedNonGeneratedKeyMissingAccountNameandIssuerandPadding(t logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), - testAccStepReadCreds(t, b, config.StorageView, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), + testAccStepReadCreds(t, b, config.StorageView, "test", expected, obsRecorder), }, }) } func TestBackend_generatedKeyInvalidSkew(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -843,14 +932,16 @@ func TestBackend_generatedKeyInvalidSkew(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_generatedKeyInvalidQRSize(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -867,14 +958,16 @@ func TestBackend_generatedKeyInvalidQRSize(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_generatedKeyInvalidKeySize(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -891,14 +984,16 @@ func TestBackend_generatedKeyInvalidKeySize(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_generatedKeyMissingAccountName(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -913,14 +1008,16 @@ func TestBackend_generatedKeyMissingAccountName(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_generatedKeyMissingIssuer(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -935,14 +1032,16 @@ func TestBackend_generatedKeyMissingIssuer(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_invalidURLValue(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -957,14 +1056,16 @@ func TestBackend_invalidURLValue(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_urlAndGenerateTrue(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -979,14 +1080,16 @@ func TestBackend_urlAndGenerateTrue(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_keyAndGenerateTrue(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -1001,14 +1104,16 @@ func TestBackend_keyAndGenerateTrue(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, true), - testAccStepReadKey(t, "test", nil), + testAccStepCreateKey(t, "test", keyData, true, obsRecorder), + testAccStepReadKey(t, "test", nil, obsRecorder), }, }) } func TestBackend_generatedKeyExportedFalse(t *testing.T) { config := logical.TestBackendConfig() + obsRecorder := observations.NewTestObservationRecorder() + config.ObservationRecorder = obsRecorder config.StorageView = &logical.InmemStorage{} b, err := Factory(context.Background(), config) if err != nil { @@ -1033,18 +1138,22 @@ func TestBackend_generatedKeyExportedFalse(t *testing.T) { logicaltest.Test(t, logicaltest.TestCase{ LogicalBackend: b, Steps: []logicaltest.TestStep{ - testAccStepCreateKey(t, "test", keyData, false), - testAccStepReadKey(t, "test", expected), + testAccStepCreateKey(t, "test", keyData, false, obsRecorder), + testAccStepReadKey(t, "test", expected, obsRecorder), }, }) } -func testAccStepCreateKey(t *testing.T, name string, keyData map[string]interface{}, expectFail bool) logicaltest.TestStep { +func testAccStepCreateKey(t *testing.T, name string, keyData map[string]interface{}, expectFail bool, obsRecorder *observations.TestObservationRecorder) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: path.Join("keys", name), Data: keyData, ErrorOk: expectFail, + PreFlight: func(req *logical.Request) error { + obsRecorder.Reset() + return nil + }, Check: func(resp *logical.Response) error { // Skip this if the key is not generated by vault or if the test is expected to fail if !keyData["generate"].(bool) || expectFail { @@ -1106,22 +1215,56 @@ func testAccStepCreateKey(t *testing.T, name string, keyData map[string]interfac t.Fatal("incorrect key string length") } + lastObservation := obsRecorder.LastObservationOfType(ObservationTypeTOTPKeyCreate) + if lastObservation == nil { + t.Fatal("missing observation") + } + obsKeyName, ok := lastObservation.Data["key_name"] + if !ok { + t.Fatal("observation is missing key_name") + } + if obsKeyName != name { + t.Fatalf("observation key_name %q != test name %q", obsKeyName, name) + } + return nil }, } } -func testAccStepDeleteKey(t *testing.T, name string) logicaltest.TestStep { +func testAccStepDeleteKey(t *testing.T, name string, obsRecorder *observations.TestObservationRecorder) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.DeleteOperation, Path: path.Join("keys", name), + PreFlight: func(req *logical.Request) error { + obsRecorder.Reset() + return nil + }, + Check: func(resp *logical.Response) error { + lastObservation := obsRecorder.LastObservationOfType(ObservationTypeTOTPKeyDelete) + if lastObservation == nil { + t.Fatal("missing observation") + } + obsKeyName, ok := lastObservation.Data["key_name"] + if !ok { + t.Fatal("observation is missing key_name") + } + if obsKeyName != name { + t.Fatalf("observation key_name %q != test name %q", obsKeyName, name) + } + return nil + }, } } -func testAccStepReadCreds(t *testing.T, b logical.Backend, s logical.Storage, name string, validation map[string]interface{}) logicaltest.TestStep { +func testAccStepReadCreds(t *testing.T, b logical.Backend, s logical.Storage, name string, validation map[string]interface{}, obsRecorder *observations.TestObservationRecorder) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: path.Join("code", name), + PreFlight: func(req *logical.Request) error { + obsRecorder.Reset() + return nil + }, Check: func(resp *logical.Response) error { var d struct { Code string `mapstructure:"code"` @@ -1149,15 +1292,35 @@ func testAccStepReadCreds(t *testing.T, b logical.Backend, s logical.Storage, na t.Fatalf("generated code isn't valid") } + lastObservation := obsRecorder.LastObservationOfType(ObservationTypeTOTPCodeGenerate) + if lastObservation == nil { + t.Fatal("missing observation") + } + obsKeyName, ok := lastObservation.Data["key_name"] + if !ok { + t.Fatal("observation is missing key_name") + } + if obsKeyName != name { + t.Fatalf("observation key_name %q != test name %q", obsKeyName, name) + } + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyRead)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyCreate)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyDelete)) + require.Equal(t, 1, obsRecorder.NumObservationsByType(ObservationTypeTOTPCodeGenerate)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPCodeValidate)) return nil }, } } -func testAccStepReadKey(t *testing.T, name string, expected map[string]interface{}) logicaltest.TestStep { +func testAccStepReadKey(t *testing.T, name string, expected map[string]interface{}, obsRecorder *observations.TestObservationRecorder) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.ReadOperation, Path: "keys/" + name, + PreFlight: func(req *logical.Request) error { + obsRecorder.Reset() + return nil + }, Check: func(resp *logical.Response) error { if resp == nil { if expected == nil { @@ -1202,12 +1365,30 @@ func testAccStepReadKey(t *testing.T, name string, expected map[string]interface case d.Digits != expected["digits"]: return fmt.Errorf("digits should equal: %d", expected["digits"]) } + + lastObservation := obsRecorder.LastObservationOfType(ObservationTypeTOTPKeyRead) + if lastObservation == nil { + t.Fatal("missing observation") + } + obsKeyName, ok := lastObservation.Data["key_name"] + if !ok { + t.Fatal("observation is missing key_name") + } + if obsKeyName != name { + t.Fatalf("observation key_name %q != test name %q", obsKeyName, name) + } + require.Equal(t, 1, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyRead)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyCreate)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyDelete)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPCodeGenerate)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPCodeValidate)) + return nil }, } } -func testAccStepValidateCode(t *testing.T, name string, code string, valid, expectError bool) logicaltest.TestStep { +func testAccStepValidateCode(t *testing.T, name string, code string, valid, expectError bool, obsRecorder *observations.TestObservationRecorder) logicaltest.TestStep { return logicaltest.TestStep{ Operation: logical.UpdateOperation, Path: "code/" + name, @@ -1215,6 +1396,10 @@ func testAccStepValidateCode(t *testing.T, name string, code string, valid, expe "code": code, }, ErrorOk: expectError, + PreFlight: func(req *logical.Request) error { + obsRecorder.Reset() + return nil + }, Check: func(resp *logical.Response) error { if resp == nil { return fmt.Errorf("bad: %#v", resp) @@ -1233,6 +1418,22 @@ func testAccStepValidateCode(t *testing.T, name string, code string, valid, expe if d.Valid != true { return fmt.Errorf("code was not valid: %s", code) } + lastObservation := obsRecorder.LastObservationOfType(ObservationTypeTOTPCodeValidate) + if lastObservation == nil { + t.Fatal("missing observation") + } + obsKeyName, ok := lastObservation.Data["key_name"] + if !ok { + t.Fatal("observation is missing key_name") + } + if obsKeyName != name { + t.Fatalf("observation key_name %q != test name %q", obsKeyName, name) + } + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyRead)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyCreate)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPKeyDelete)) + require.Equal(t, 0, obsRecorder.NumObservationsByType(ObservationTypeTOTPCodeGenerate)) + require.Equal(t, 1, obsRecorder.NumObservationsByType(ObservationTypeTOTPCodeValidate)) default: if d.Valid != false { diff --git a/builtin/logical/totp/observation_consts.go b/builtin/logical/totp/observation_consts.go new file mode 100644 index 0000000000..1af282e2ee --- /dev/null +++ b/builtin/logical/totp/observation_consts.go @@ -0,0 +1,12 @@ +// Copyright IBM Corp. 2016, 2025 +// SPDX-License-Identifier: BUSL-1.1 + +package totp + +const ( + ObservationTypeTOTPKeyRead = "totp/key/read" + ObservationTypeTOTPKeyCreate = "totp/key/create" + ObservationTypeTOTPKeyDelete = "totp/key/delete" + ObservationTypeTOTPCodeGenerate = "totp/code/generate" + ObservationTypeTOTPCodeValidate = "totp/code/validate" +) diff --git a/builtin/logical/totp/path_code.go b/builtin/logical/totp/path_code.go index 2502fafdf0..0dc9def48c 100644 --- a/builtin/logical/totp/path_code.go +++ b/builtin/logical/totp/path_code.go @@ -77,6 +77,10 @@ func (b *backend) pathReadCode(ctx context.Context, req *logical.Request, data * return nil, err } + b.TryRecordObservationWithRequest(ctx, req, ObservationTypeTOTPCodeGenerate, map[string]interface{}{ + "key_name": name, + }) + // Return the secret return &logical.Response{ Data: map[string]interface{}{ @@ -120,6 +124,11 @@ func (b *backend) pathValidateCode(ctx context.Context, req *logical.Request, da return logical.ErrorResponse("an error occurred while validating the code"), err } + b.TryRecordObservationWithRequest(ctx, req, ObservationTypeTOTPCodeValidate, map[string]interface{}{ + "key_name": name, + "valid": valid, + }) + // Take the key skew, add two for behind and in front, and multiple that by // the period to cover the full possibility of the validity of the key err = b.usedCodes.Add(usedName, nil, time.Duration( diff --git a/builtin/logical/totp/path_keys.go b/builtin/logical/totp/path_keys.go index 22548d649d..f6d6a00628 100644 --- a/builtin/logical/totp/path_keys.go +++ b/builtin/logical/totp/path_keys.go @@ -166,16 +166,22 @@ func (b *backend) Key(ctx context.Context, s logical.Storage, n string) (*keyEnt } func (b *backend) pathKeyDelete(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - err := req.Storage.Delete(ctx, "key/"+data.Get("name").(string)) + name := data.Get("name").(string) + err := req.Storage.Delete(ctx, "key/"+name) if err != nil { return nil, err } + b.TryRecordObservationWithRequest(ctx, req, ObservationTypeTOTPKeyDelete, map[string]interface{}{ + "key_name": name, + }) + return nil, nil } func (b *backend) pathKeyRead(ctx context.Context, req *logical.Request, data *framework.FieldData) (*logical.Response, error) { - key, err := b.Key(ctx, req.Storage, data.Get("name").(string)) + name := data.Get("name").(string) + key, err := b.Key(ctx, req.Storage, name) if err != nil { return nil, err } @@ -186,6 +192,14 @@ func (b *backend) pathKeyRead(ctx context.Context, req *logical.Request, data *f // Translate algorithm back to string algorithm := key.Algorithm.String() + b.TryRecordObservationWithRequest(ctx, req, ObservationTypeTOTPKeyRead, map[string]interface{}{ + "key_name": name, + "period": key.Period, + "algorithm": algorithm, + "digits": key.Digits, + "skew": key.Skew, + }) + // Return values of key return &logical.Response{ Data: map[string]interface{}{ @@ -432,6 +446,17 @@ func (b *backend) pathKeyCreate(ctx context.Context, req *logical.Request, data return nil, err } + b.TryRecordObservationWithRequest(ctx, req, ObservationTypeTOTPKeyCreate, map[string]interface{}{ + "key_name": name, + "period": period, + "algorithm": algorithm, + "digits": digits, + "skew": skew, + "generate": generate, + "exported": exported, + "key_size": keySize, + }) + return response, nil } diff --git a/sdk/helper/testhelpers/observations/recorder.go b/sdk/helper/testhelpers/observations/recorder.go index b4f2c0690a..82f494e2ea 100644 --- a/sdk/helper/testhelpers/observations/recorder.go +++ b/sdk/helper/testhelpers/observations/recorder.go @@ -75,3 +75,10 @@ func (t *TestObservationRecorder) LastObservationOfType(observationType string) } return ofType[len(ofType)-1] } + +// Reset empties the list of recorded observations. +func (t *TestObservationRecorder) Reset() { + t.l.Lock() + defer t.l.Unlock() + t.observationsByType = make(map[string][]*TestObservation) +}