From c925324057657efe644ea56b09f17df5e09d5093 Mon Sep 17 00:00:00 2001 From: Chris Capurso Date: Fri, 7 Jan 2022 14:46:26 -0500 Subject: [PATCH] add retry logic to TestKV_Patch_RootToken (#13586) --- http/handler_test.go | 195 +------------------- vault/external_tests/kv/kv_patch_test.go | 225 ++++++++++++++++++++++- 2 files changed, 223 insertions(+), 197 deletions(-) diff --git a/http/handler_test.go b/http/handler_test.go index 9cefe5aaa..ff2383ea8 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -13,14 +13,9 @@ import ( "reflect" "strings" "testing" - "time" "github.com/go-test/deep" "github.com/hashicorp/go-cleanhttp" - kv "github.com/hashicorp/vault-plugin-secrets-kv" - "github.com/hashicorp/vault/api" - "github.com/hashicorp/vault/audit" - auditFile "github.com/hashicorp/vault/builtin/audit/file" "github.com/hashicorp/vault/helper/namespace" "github.com/hashicorp/vault/sdk/helper/consts" "github.com/hashicorp/vault/sdk/logical" @@ -863,192 +858,4 @@ func TestHandler_Parse_Form(t *testing.T) { if diff := deep.Equal(expected, apiResp.Data); diff != nil { t.Fatal(diff) } -} - -func TestHandler_Patch_BadContentTypeHeader(t *testing.T) { - coreConfig := &vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "kv": kv.VersionedKVFactory, - }, - } - - cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ - HandlerFunc: Handler, - }) - - cluster.Start() - defer cluster.Cleanup() - - cores := cluster.Cores - - core := cores[0].Core - c := cluster.Cores[0].Client - vault.TestWaitActive(t, core) - - // Mount a KVv2 backend - err := c.Sys().Mount("kv", &api.MountInput{ - Type: "kv-v2", - }) - if err != nil { - t.Fatal(err) - } - - kvData := map[string]interface{}{ - "data": map[string]interface{}{ - "bar": "a", - }, - } - - resp, err := c.Logical().Write("kv/data/foo", kvData) - if err != nil { - t.Fatalf("write failed - err :%#v, resp: %#v\n", err, resp) - } - - resp, err = c.Logical().Read("kv/data/foo") - if err != nil { - t.Fatalf("read failed - err :%#v, resp: %#v\n", err, resp) - } - - req := c.NewRequest("PATCH", "/v1/kv/data/foo") - req.Headers = http.Header{ - "Content-Type": []string{"application/json"}, - } - - if err := req.SetJSONBody(kvData); err != nil { - t.Fatal(err) - } - - apiResp, err := c.RawRequestWithContext(context.Background(), req) - if err == nil || apiResp.StatusCode != http.StatusUnsupportedMediaType { - t.Fatalf("expected PATCH request to fail with %d status code - err :%#v, resp: %#v\n", http.StatusUnsupportedMediaType, err, apiResp) - } -} - -func kvRequestWithRetry(t *testing.T, req func() (*api.Secret, error)) (*api.Secret, error) { - t.Helper() - - var err error - var resp *api.Secret - - // Loop until return message does not indicate upgrade, or timeout. - timeout := time.After(20 * time.Second) - ticker := time.Tick(time.Second) - - for { - select { - case <-timeout: - t.Error("timeout expired waiting for upgrade") - case <-ticker: - resp, err = req() - - if err == nil { - return resp, nil - } - - responseError := err.(*api.ResponseError) - if !strings.Contains(responseError.Error(), "Upgrading from non-versioned to versioned data") { - return resp, err - } - } - } -} - -func TestHandler_Patch_Audit(t *testing.T) { - coreConfig := &vault.CoreConfig{ - LogicalBackends: map[string]logical.Factory{ - "kv": kv.VersionedKVFactory, - }, - AuditBackends: map[string]audit.Factory{ - "file": auditFile.Factory, - }, - } - - cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ - HandlerFunc: Handler, - }) - - cluster.Start() - defer cluster.Cleanup() - - cores := cluster.Cores - - core := cores[0].Core - c := cluster.Cores[0].Client - vault.TestWaitActive(t, core) - - if err := c.Sys().Mount("kv/", &api.MountInput{ - Type: "kv-v2", - }); err != nil { - t.Fatalf("kv-v2 mount attempt failed - err: %#v\n", err) - } - - auditLogFile, err := ioutil.TempFile("", "httppatch") - if err != nil { - t.Fatal(err) - } - - err = c.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{ - Type: "file", - Options: map[string]string{ - "file_path": auditLogFile.Name(), - }, - }) - if err != nil { - t.Fatal(err) - } - - writeData := map[string]interface{}{ - "data": map[string]interface{}{ - "bar": "a", - }, - } - - resp, err := kvRequestWithRetry(t, func() (*api.Secret, error) { - return c.Logical().Write("kv/data/foo", writeData) - }) - - if err != nil { - t.Fatalf("write request failed, err: %#v, resp: %#v\n", err, resp) - } - - patchData := map[string]interface{}{ - "data": map[string]interface{}{ - "baz": "b", - }, - } - - resp, err = kvRequestWithRetry(t, func() (*api.Secret, error) { - return c.Logical().JSONMergePatch(context.Background(), "kv/data/foo", patchData) - }) - - if err != nil { - t.Fatalf("patch request failed, err: %#v, resp: %#v\n", err, resp) - } - - patchRequestLogCount := 0 - patchResponseLogCount := 0 - decoder := json.NewDecoder(auditLogFile) - - var auditRecord map[string]interface{} - for decoder.Decode(&auditRecord) == nil { - auditRequest := map[string]interface{}{} - - if req, ok := auditRecord["request"]; ok { - auditRequest = req.(map[string]interface{}) - } - - if auditRequest["operation"] == "patch" && auditRecord["type"] == "request" { - patchRequestLogCount += 1 - } else if auditRequest["operation"] == "patch" && auditRecord["type"] == "response" { - patchResponseLogCount += 1 - } - } - - if patchRequestLogCount != 1 { - t.Fatalf("expected 1 patch request audit log record, saw %d\n", patchRequestLogCount) - } - - if patchResponseLogCount != 1 { - t.Fatalf("expected 1 patch response audit log record, saw %d\n", patchResponseLogCount) - } -} +} \ No newline at end of file diff --git a/vault/external_tests/kv/kv_patch_test.go b/vault/external_tests/kv/kv_patch_test.go index 33d36f4f0..d61a3552e 100644 --- a/vault/external_tests/kv/kv_patch_test.go +++ b/vault/external_tests/kv/kv_patch_test.go @@ -2,15 +2,210 @@ package kv import ( "context" + "encoding/json" + "io/ioutil" + "net/http" + "strings" "testing" + "time" logicalKv "github.com/hashicorp/vault-plugin-secrets-kv" "github.com/hashicorp/vault/api" + "github.com/hashicorp/vault/audit" + auditFile "github.com/hashicorp/vault/builtin/audit/file" vaulthttp "github.com/hashicorp/vault/http" "github.com/hashicorp/vault/sdk/logical" "github.com/hashicorp/vault/vault" ) +func TestKV_Patch_BadContentTypeHeader(t *testing.T) { + coreConfig := &vault.CoreConfig{ + LogicalBackends: map[string]logical.Factory{ + "kv": logicalKv.VersionedKVFactory, + }, + } + + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + HandlerFunc: vaulthttp.Handler, + }) + + cluster.Start() + defer cluster.Cleanup() + + cores := cluster.Cores + + core := cores[0].Core + c := cluster.Cores[0].Client + vault.TestWaitActive(t, core) + + // Mount a KVv2 backend + err := c.Sys().Mount("kv", &api.MountInput{ + Type: "kv-v2", + }) + if err != nil { + t.Fatal(err) + } + + kvData := map[string]interface{}{ + "data": map[string]interface{}{ + "bar": "a", + }, + } + + resp, err := c.Logical().Write("kv/data/foo", kvData) + if err != nil { + t.Fatalf("write failed - err :%#v, resp: %#v\n", err, resp) + } + + resp, err = c.Logical().Read("kv/data/foo") + if err != nil { + t.Fatalf("read failed - err :%#v, resp: %#v\n", err, resp) + } + + req := c.NewRequest("PATCH", "/v1/kv/data/foo") + req.Headers = http.Header{ + "Content-Type": []string{"application/json"}, + } + + if err := req.SetJSONBody(kvData); err != nil { + t.Fatal(err) + } + + apiResp, err := c.RawRequestWithContext(context.Background(), req) + if err == nil || apiResp.StatusCode != http.StatusUnsupportedMediaType { + t.Fatalf("expected PATCH request to fail with %d status code - err :%#v, resp: %#v\n", http.StatusUnsupportedMediaType, err, apiResp) + } +} + +func kvRequestWithRetry(t *testing.T, req func() (*api.Secret, error)) (*api.Secret, error) { + t.Helper() + + var err error + var resp *api.Secret + + // Loop until return message does not indicate upgrade, or timeout. + timeout := time.After(20 * time.Second) + ticker := time.Tick(time.Second) + + for { + select { + case <-timeout: + t.Error("timeout expired waiting for upgrade") + case <-ticker: + resp, err = req() + + if err == nil { + return resp, nil + } + + responseError := err.(*api.ResponseError) + if !strings.Contains(responseError.Error(), "Upgrading from non-versioned to versioned data") { + return resp, err + } + } + } +} + +func TestKV_Patch_Audit(t *testing.T) { + coreConfig := &vault.CoreConfig{ + LogicalBackends: map[string]logical.Factory{ + "kv": logicalKv.VersionedKVFactory, + }, + AuditBackends: map[string]audit.Factory{ + "file": auditFile.Factory, + }, + } + + cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{ + HandlerFunc: vaulthttp.Handler, + }) + + cluster.Start() + defer cluster.Cleanup() + + cores := cluster.Cores + + core := cores[0].Core + c := cluster.Cores[0].Client + vault.TestWaitActive(t, core) + + if err := c.Sys().Mount("kv/", &api.MountInput{ + Type: "kv-v2", + }); err != nil { + t.Fatalf("kv-v2 mount attempt failed - err: %#v\n", err) + } + + auditLogFile, err := ioutil.TempFile("", "httppatch") + if err != nil { + t.Fatal(err) + } + + err = c.Sys().EnableAuditWithOptions("file", &api.EnableAuditOptions{ + Type: "file", + Options: map[string]string{ + "file_path": auditLogFile.Name(), + }, + }) + if err != nil { + t.Fatal(err) + } + + writeData := map[string]interface{}{ + "data": map[string]interface{}{ + "bar": "a", + }, + } + + resp, err := kvRequestWithRetry(t, func() (*api.Secret, error) { + return c.Logical().Write("kv/data/foo", writeData) + }) + + if err != nil { + t.Fatalf("write request failed, err: %#v, resp: %#v\n", err, resp) + } + + patchData := map[string]interface{}{ + "data": map[string]interface{}{ + "baz": "b", + }, + } + + resp, err = kvRequestWithRetry(t, func() (*api.Secret, error) { + return c.Logical().JSONMergePatch(context.Background(), "kv/data/foo", patchData) + }) + + if err != nil { + t.Fatalf("patch request failed, err: %#v, resp: %#v\n", err, resp) + } + + patchRequestLogCount := 0 + patchResponseLogCount := 0 + decoder := json.NewDecoder(auditLogFile) + + var auditRecord map[string]interface{} + for decoder.Decode(&auditRecord) == nil { + auditRequest := map[string]interface{}{} + + if req, ok := auditRecord["request"]; ok { + auditRequest = req.(map[string]interface{}) + } + + if auditRequest["operation"] == "patch" && auditRecord["type"] == "request" { + patchRequestLogCount += 1 + } else if auditRequest["operation"] == "patch" && auditRecord["type"] == "response" { + patchResponseLogCount += 1 + } + } + + if patchRequestLogCount != 1 { + t.Fatalf("expected 1 patch request audit log record, saw %d\n", patchRequestLogCount) + } + + if patchResponseLogCount != 1 { + t.Fatalf("expected 1 patch response audit log record, saw %d\n", patchResponseLogCount) + } +} + // Verifies that patching works by default with the root token func TestKV_Patch_RootToken(t *testing.T) { coreConfig := &vault.CoreConfig{ @@ -39,17 +234,41 @@ func TestKV_Patch_RootToken(t *testing.T) { } // Write a kv value and patch it - _, err = client.Logical().Write("kv/data/foo", map[string]interface{}{"data": map[string]interface{}{"bar": "baz"}}) + _, err = kvRequestWithRetry(t, func() (*api.Secret, error) { + data := map[string]interface{}{ + "data": map[string]interface{}{ + "bar": "baz", + }, + } + + return client.Logical().Write("kv/data/foo", data) + }) + if err != nil { t.Fatal(err) } - _, err = client.Logical().JSONMergePatch(context.Background(), "kv/data/foo", map[string]interface{}{"data": map[string]interface{}{"bar": "quux"}}) + _, err = kvRequestWithRetry(t, func() (*api.Secret, error) { + data := map[string]interface{}{ + "data": map[string]interface{}{ + "bar": "quux", + }, + } + return client.Logical().JSONMergePatch(context.Background(), "kv/data/foo", data) + }) + + if err != nil { + t.Fatal(err) + } + + secret, err := kvRequestWithRetry(t, func() (*api.Secret, error) { + return client.Logical().Read("kv/data/foo") + }) + if err != nil { t.Fatal(err) } - secret, err := client.Logical().Read("kv/data/foo") bar := secret.Data["data"].(map[string]interface{})["bar"] if bar != "quux" { t.Fatalf("expected bar to be quux but it was %q", bar)