// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: BUSL-1.1 package agent import ( "bytes" "fmt" "net/http" "net/http/httptest" "reflect" "testing" "github.com/hashicorp/consul/testrpc" "github.com/hashicorp/consul/agent/structs" ) func TestKVSEndpoint_PUT_GET_DELETE(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() keys := []string{ "baz", "bar", "foo/sub1", "foo/sub2", "zip", } for _, key := range keys { buf := bytes.NewBuffer([]byte("test")) req, _ := http.NewRequest("PUT", "/v1/kv/"+key, buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } } for _, key := range keys { req, _ := http.NewRequest("GET", "/v1/kv/"+key, nil) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } assertIndex(t, resp) res, ok := obj.(structs.DirEntries) if !ok { t.Fatalf("should work") } if len(res) != 1 { t.Fatalf("bad: %v", res) } if res[0].Key != key { t.Fatalf("bad: %v", res) } } for _, key := range keys { req, _ := http.NewRequest("DELETE", "/v1/kv/"+key, nil) resp := httptest.NewRecorder() if _, err := a.srv.KVSEndpoint(resp, req); err != nil { t.Fatalf("err: %v", err) } } } func TestKVSEndpoint_Recurse(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() keys := []string{ "bar", "baz", "foo/sub1", "foo/sub2", "zip", } for _, key := range keys { buf := bytes.NewBuffer([]byte("test")) req, _ := http.NewRequest("PUT", "/v1/kv/"+key, buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } } { // Get all the keys req, _ := http.NewRequest("GET", "/v1/kv/?recurse", nil) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } assertIndex(t, resp) res, ok := obj.(structs.DirEntries) if !ok { t.Fatalf("should work") } if len(res) != len(keys) { t.Fatalf("bad: %v", res) } for idx, key := range keys { if res[idx].Key != key { t.Fatalf("bad: %v %v", res[idx].Key, key) } } } { req, _ := http.NewRequest("DELETE", "/v1/kv/?recurse", nil) resp := httptest.NewRecorder() if _, err := a.srv.KVSEndpoint(resp, req); err != nil { t.Fatalf("err: %v", err) } } { // Get all the keys req, _ := http.NewRequest("GET", "/v1/kv/?recurse", nil) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if obj != nil { t.Fatalf("bad: %v", obj) } } } func TestKVSEndpoint_DELETE_CAS(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() { buf := bytes.NewBuffer([]byte("test")) req, _ := http.NewRequest("PUT", "/v1/kv/test", buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } } req, _ := http.NewRequest("GET", "/v1/kv/test", nil) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } d := obj.(structs.DirEntries)[0] // Create a CAS request, bad index { buf := bytes.NewBuffer([]byte("zip")) req, _ := http.NewRequest("DELETE", fmt.Sprintf("/v1/kv/test?cas=%d", d.ModifyIndex-1), buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); res { t.Fatalf("should NOT work") } } // Create a CAS request, good index { buf := bytes.NewBuffer([]byte("zip")) req, _ := http.NewRequest("DELETE", fmt.Sprintf("/v1/kv/test?cas=%d", d.ModifyIndex), buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } } // Verify the delete req, _ = http.NewRequest("GET", "/v1/kv/test", nil) resp = httptest.NewRecorder() obj, _ = a.srv.KVSEndpoint(resp, req) if obj != nil { t.Fatalf("should be destroyed") } } func TestKVSEndpoint_CAS(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() { buf := bytes.NewBuffer([]byte("test")) req, _ := http.NewRequest("PUT", "/v1/kv/test?flags=50", buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } } req, _ := http.NewRequest("GET", "/v1/kv/test", nil) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } d := obj.(structs.DirEntries)[0] // Check the flags if d.Flags != 50 { t.Fatalf("bad: %v", d) } // Create a CAS request, bad index { buf := bytes.NewBuffer([]byte("zip")) req, _ := http.NewRequest("PUT", fmt.Sprintf("/v1/kv/test?flags=42&cas=%d", d.ModifyIndex-1), buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); res { t.Fatalf("should NOT work") } } // Create a CAS request, good index { buf := bytes.NewBuffer([]byte("zip")) req, _ := http.NewRequest("PUT", fmt.Sprintf("/v1/kv/test?flags=42&cas=%d", d.ModifyIndex), buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } } // Verify the update req, _ = http.NewRequest("GET", "/v1/kv/test", nil) resp = httptest.NewRecorder() obj, _ = a.srv.KVSEndpoint(resp, req) d = obj.(structs.DirEntries)[0] if d.Flags != 42 { t.Fatalf("bad: %v", d) } if string(d.Value) != "zip" { t.Fatalf("bad: %v", d) } } func TestKVSEndpoint_ListKeys(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() keys := []string{ "bar", "baz", "foo/sub1", "foo/sub2", "zip", } for _, key := range keys { buf := bytes.NewBuffer([]byte("test")) req, _ := http.NewRequest("PUT", "/v1/kv/"+key, buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } } { // Get all the keys req, _ := http.NewRequest("GET", "/v1/kv/?keys&seperator=/", nil) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } assertIndex(t, resp) res, ok := obj.([]string) if !ok { t.Fatalf("should work") } expect := []string{"bar", "baz", "foo/", "zip"} if !reflect.DeepEqual(res, expect) { t.Fatalf("bad: %v", res) } } } func TestKVSEndpoint_AcquireRelease(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() testrpc.WaitForTestAgent(t, a.RPC, "dc1") // Acquire the lock id := makeTestSession(t, a.srv) req, _ := http.NewRequest("PUT", "/v1/kv/test?acquire="+id, bytes.NewReader(nil)) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } // Verify we have the lock req, _ = http.NewRequest("GET", "/v1/kv/test", nil) resp = httptest.NewRecorder() obj, err = a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } d := obj.(structs.DirEntries)[0] // Check the flags if d.Session != id { t.Fatalf("bad: %v", d) } // Release the lock req, _ = http.NewRequest("PUT", "/v1/kv/test?release="+id, bytes.NewReader(nil)) resp = httptest.NewRecorder() obj, err = a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } // Verify we do not have the lock req, _ = http.NewRequest("GET", "/v1/kv/test", nil) resp = httptest.NewRecorder() obj, err = a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } d = obj.(structs.DirEntries)[0] // Check the flags if d.Session != "" { t.Fatalf("bad: %v", d) } } func TestKVSEndpoint_GET_Raw(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() buf := bytes.NewBuffer([]byte("test")) req, _ := http.NewRequest("PUT", "/v1/kv/test", buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } req, _ = http.NewRequest("GET", "/v1/kv/test?raw", nil) resp = httptest.NewRecorder() _, err = a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } assertIndex(t, resp) // Check the headers contentTypeHdr := resp.Header().Values("Content-Type") if len(contentTypeHdr) != 1 { t.Fatalf("expected 1 value for Content-Type header, got %d: %+v", len(contentTypeHdr), contentTypeHdr) } if contentTypeHdr[0] != "text/plain" { t.Fatalf("expected Content-Type header to be \"text/plain\", got %q", contentTypeHdr[0]) } optionsHdr := resp.Header().Values("X-Content-Type-Options") if len(optionsHdr) != 1 { t.Fatalf("expected 1 value for X-Content-Type-Options header, got %d: %+v", len(optionsHdr), optionsHdr) } if optionsHdr[0] != "nosniff" { t.Fatalf("expected X-Content-Type-Options header to be \"nosniff\", got %q", optionsHdr[0]) } cspHeader := resp.Header().Values("Content-Security-Policy") if len(cspHeader) != 1 { t.Fatalf("expected 1 value for Content-Security-Policy header, got %d: %+v", len(optionsHdr), optionsHdr) } if cspHeader[0] != "sandbox" { t.Fatalf("expected X-Content-Type-Options header to be \"sandbox\", got %q", optionsHdr[0]) } // Check the body if !bytes.Equal(resp.Body.Bytes(), []byte("test")) { t.Fatalf("bad: %s", resp.Body.Bytes()) } } func TestKVSEndpoint_PUT_ConflictingFlags(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() req, _ := http.NewRequest("PUT", "/v1/kv/test?cas=0&acquire=xxx", nil) resp := httptest.NewRecorder() if _, err := a.srv.KVSEndpoint(resp, req); err != nil { t.Fatalf("err: %v", err) } if resp.Code != 400 { t.Fatalf("expected 400, got %d", resp.Code) } if !bytes.Contains(resp.Body.Bytes(), []byte("Conflicting")) { t.Fatalf("expected conflicting args error") } } func TestKVSEndpoint_GET(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() buf := bytes.NewBuffer([]byte("test")) req, _ := http.NewRequest("PUT", "/v1/kv/test", buf) resp := httptest.NewRecorder() obj, err := a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } if res := obj.(bool); !res { t.Fatalf("should work") } req, _ = http.NewRequest("GET", "/v1/kv/test", nil) resp = httptest.NewRecorder() _, err = a.srv.KVSEndpoint(resp, req) if err != nil { t.Fatalf("err: %v", err) } assertIndex(t, resp) // The following headers are only included when returning a raw KV response contentTypeHdr := resp.Header().Values("Content-Type") if len(contentTypeHdr) != 0 { t.Fatalf("expected no Content-Type header, got %d: %+v", len(contentTypeHdr), contentTypeHdr) } optionsHdr := resp.Header().Values("X-Content-Type-Options") if len(optionsHdr) != 0 { t.Fatalf("expected no X-Content-Type-Options header, got %d: %+v", len(optionsHdr), optionsHdr) } cspHeader := resp.Header().Values("Content-Security-Policy") if len(cspHeader) != 0 { t.Fatalf("expected no Content-Security-Policy header, got %d: %+v", len(optionsHdr), optionsHdr) } } func TestKVSEndpoint_DELETE_ConflictingFlags(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() a := NewTestAgent(t, "") defer a.Shutdown() req, _ := http.NewRequest("DELETE", "/v1/kv/test?recurse&cas=0", nil) resp := httptest.NewRecorder() if _, err := a.srv.KVSEndpoint(resp, req); err != nil { t.Fatalf("err: %v", err) } if resp.Code != 400 { t.Fatalf("expected 400, got %d", resp.Code) } if !bytes.Contains(resp.Body.Bytes(), []byte("Conflicting")) { t.Fatalf("expected conflicting args error") } }