e5ac6464f6
Move the secure variables quota enforcement calls into the state store to ensure quota checks are atomic with quota updates (in the same transaction). Switch to a machine-size int instead of a uint64 for quota tracking. The ENT-side quota spec is described as int, and negative values have a meaning as "not permitted at all". Using the same type for tracking will make it easier to the math around checks, and uint64 is infeasibly large anyways. Add secure vars to quota HTTP API and CLI outputs and API docs.
640 lines
20 KiB
Go
640 lines
20 KiB
Go
package state
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
memdb "github.com/hashicorp/go-memdb"
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestStateStore_GetSecureVariable(t *testing.T) {
|
|
ci.Parallel(t)
|
|
testState := testStateStore(t)
|
|
ws := memdb.NewWatchSet()
|
|
sve, err := testState.GetSecureVariable(ws, "default", "not/a/path")
|
|
require.NoError(t, err)
|
|
require.Nil(t, sve)
|
|
}
|
|
|
|
func TestStateStore_UpsertSecureVariables(t *testing.T) {
|
|
ci.Parallel(t)
|
|
testState := testStateStore(t)
|
|
ws := memdb.NewWatchSet()
|
|
|
|
svs, svm := mockSecureVariables(2)
|
|
t.Log(printSecureVariables(svs))
|
|
insertIndex := uint64(20)
|
|
|
|
var expectedQuotaSize int
|
|
for _, v := range svs {
|
|
expectedQuotaSize += len(v.Data)
|
|
}
|
|
|
|
// Ensure new secure variables are inserted as expected with their
|
|
// correct indexes, along with an update to the index table.
|
|
t.Run("1 create new variables", func(t *testing.T) {
|
|
|
|
// Perform the initial upsert of secure variables.
|
|
err := testState.UpsertSecureVariables(structs.MsgTypeTestSetup, insertIndex, svs)
|
|
require.NoError(t, err)
|
|
|
|
// Check that the index for the table was modified as expected.
|
|
initialIndex, err := testState.Index(TableSecureVariables)
|
|
require.NoError(t, err)
|
|
require.Equal(t, insertIndex, initialIndex)
|
|
|
|
// List all the secure variables in the table, so we can perform a
|
|
// number of tests on the return array.
|
|
|
|
iter, err := testState.SecureVariables(ws)
|
|
require.NoError(t, err)
|
|
|
|
// Count how many table entries we have, to ensure it is the expected
|
|
// number.
|
|
var count int
|
|
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
count++
|
|
|
|
// Ensure the create and modify indexes are populated correctly.
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
require.Equal(t, insertIndex, sv.CreateIndex, "incorrect create index", sv.Path)
|
|
require.Equal(t, insertIndex, sv.ModifyIndex, "incorrect modify index", sv.Path)
|
|
// update the mock element so the test element has the correct create/modify
|
|
// indexes and times now that we have validated them
|
|
nv := sv.Copy()
|
|
svm[sv.Path] = &nv
|
|
}
|
|
require.Equal(t, len(svs), count, "incorrect number of secure variables found")
|
|
|
|
quotaUsed, err := testState.SecureVariablesQuotaByNamespace(ws, structs.DefaultNamespace)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(expectedQuotaSize), quotaUsed.Size)
|
|
})
|
|
|
|
svs = svm.List()
|
|
t.Log(printSecureVariables(svs))
|
|
t.Run("1a fetch variable", func(t *testing.T) {
|
|
sve, err := testState.GetSecureVariable(ws, svs[0].Namespace, svs[0].Path)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, sve)
|
|
})
|
|
|
|
// Upsert the exact same secure variables without any
|
|
// modification. In this case, the index table should not be
|
|
// updated, indicating no write actually happened due to equality
|
|
// checking.
|
|
t.Run("2 upsert same", func(t *testing.T) {
|
|
reInsertIndex := uint64(30)
|
|
require.NoError(t, testState.UpsertSecureVariables(structs.MsgTypeTestSetup, reInsertIndex, svs))
|
|
reInsertActualIndex, err := testState.Index(TableSecureVariables)
|
|
require.NoError(t, err)
|
|
require.Equal(t, insertIndex, reInsertActualIndex, "index should not have changed")
|
|
|
|
quotaUsed, err := testState.SecureVariablesQuotaByNamespace(ws, structs.DefaultNamespace)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(expectedQuotaSize), quotaUsed.Size)
|
|
})
|
|
|
|
// Modify a single one of the previously inserted secure variables
|
|
// and performs an upsert. This ensures the index table is
|
|
// modified correctly and that each secure variable is updated, or
|
|
// not, as expected.
|
|
t.Run("3 modify one", func(t *testing.T) {
|
|
sv1Update := svs[0].Copy()
|
|
sv1Update.KeyID = "sv1-update"
|
|
|
|
buf := make([]byte, 1+len(sv1Update.Data))
|
|
copy(buf, sv1Update.Data)
|
|
buf[len(buf)-1] = 'x'
|
|
sv1Update.Data = buf
|
|
|
|
svs1Update := []*structs.SecureVariableEncrypted{&sv1Update}
|
|
|
|
update1Index := uint64(40)
|
|
require.NoError(t, testState.UpsertSecureVariables(structs.MsgTypeTestSetup, update1Index, svs1Update))
|
|
|
|
// Check that the index for the table was modified as expected.
|
|
updateActualIndex, err := testState.Index(TableSecureVariables)
|
|
require.NoError(t, err)
|
|
require.Equal(t, update1Index, updateActualIndex, "index should have changed")
|
|
|
|
// Get the secure variables from the table.
|
|
iter, err := testState.SecureVariables(ws)
|
|
require.NoError(t, err)
|
|
|
|
// Iterate all the stored variables and assert they are as expected.
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
t.Logf("S " + printSecureVariable(sv))
|
|
|
|
var expectedModifyIndex uint64
|
|
|
|
switch sv.Path {
|
|
case sv1Update.Path:
|
|
expectedModifyIndex = update1Index
|
|
case svs[1].Path:
|
|
expectedModifyIndex = insertIndex
|
|
default:
|
|
t.Errorf("unknown secure variable found: %s", sv.Path)
|
|
continue
|
|
}
|
|
require.Equal(t, insertIndex, sv.CreateIndex, "incorrect create index", sv.Path)
|
|
require.Equal(t, expectedModifyIndex, sv.ModifyIndex, "incorrect modify index", sv.Path)
|
|
// update the mock element so the test element has the correct create/modify
|
|
// indexes and times now that we have validated them
|
|
nv := sv.Copy()
|
|
svm[sv.Path] = &nv
|
|
}
|
|
|
|
quotaUsed, err := testState.SecureVariablesQuotaByNamespace(ws, structs.DefaultNamespace)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(expectedQuotaSize+1), quotaUsed.Size)
|
|
})
|
|
|
|
svs = svm.List()
|
|
t.Log(printSecureVariables(svs))
|
|
|
|
// Modify the second variable but send an upsert request that
|
|
// includes this and the already modified variable.
|
|
t.Run("4 upsert other", func(t *testing.T) {
|
|
update2Index := uint64(50)
|
|
sv2 := svs[1].Copy()
|
|
sv2.KeyID = "sv2-update"
|
|
sv2.ModifyIndex = update2Index
|
|
svs2Update := []*structs.SecureVariableEncrypted{svs[0], &sv2}
|
|
t.Logf("* " + printSecureVariable(svs[0]))
|
|
t.Logf("* " + printSecureVariable(&sv2))
|
|
|
|
require.NoError(t, testState.UpsertSecureVariables(structs.MsgTypeTestSetup, update2Index, svs2Update))
|
|
|
|
// Check that the index for the table was modified as expected.
|
|
update2ActualIndex, err := testState.Index(TableSecureVariables)
|
|
require.NoError(t, err)
|
|
require.Equal(t, update2Index, update2ActualIndex, "index should have changed")
|
|
|
|
// Get the secure variables from the table.
|
|
iter, err := testState.SecureVariables(ws)
|
|
require.NoError(t, err)
|
|
|
|
// Iterate all the stored variables and assert they are as expected.
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
t.Logf("S " + printSecureVariable(sv))
|
|
|
|
var (
|
|
expectedModifyIndex uint64
|
|
expectedSV *structs.SecureVariableEncrypted
|
|
)
|
|
|
|
switch sv.Path {
|
|
case sv2.Path:
|
|
expectedModifyIndex = update2Index
|
|
expectedSV = &sv2
|
|
case svs[0].Path:
|
|
expectedModifyIndex = svs[0].ModifyIndex
|
|
expectedSV = svs[0]
|
|
default:
|
|
t.Errorf("unknown secure variable found: %s", sv.Path)
|
|
continue
|
|
}
|
|
require.Equal(t, insertIndex, sv.CreateIndex, "%s: incorrect create index", sv.Path)
|
|
require.Equal(t, expectedModifyIndex, sv.ModifyIndex, "%s: incorrect modify index", sv.Path)
|
|
|
|
// update the mock element so the test element has the correct create/modify
|
|
// indexes and times now that we have validated them
|
|
expectedSV.ModifyTime = sv.ModifyTime
|
|
|
|
require.True(t, expectedSV.Equals(*sv), "Secure Variables are not equal:\n expected:%s\n received:%s\n", printSecureVariable(expectedSV), printSecureVariable(sv))
|
|
}
|
|
|
|
quotaUsed, err := testState.SecureVariablesQuotaByNamespace(ws, structs.DefaultNamespace)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(expectedQuotaSize+1), quotaUsed.Size)
|
|
|
|
})
|
|
}
|
|
|
|
func TestStateStore_DeleteSecureVariable(t *testing.T) {
|
|
ci.Parallel(t)
|
|
testState := testStateStore(t)
|
|
|
|
// Generate some test secure variables that we will use and modify throughout.
|
|
svs, _ := mockSecureVariables(2)
|
|
initialIndex := uint64(10)
|
|
|
|
t.Run("1 delete a secure variable that does not exist", func(t *testing.T) {
|
|
err := testState.DeleteSecureVariables(
|
|
structs.MsgTypeTestSetup, initialIndex, svs[0].Namespace, []string{svs[0].Path})
|
|
require.EqualError(t, err, "secure variable not found")
|
|
|
|
actualInitialIndex, err := testState.Index(TableSecureVariables)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(0), actualInitialIndex, "index should not have changed")
|
|
|
|
quotaUsed, err := testState.SecureVariablesQuotaByNamespace(nil, structs.DefaultNamespace)
|
|
require.NoError(t, err)
|
|
require.Nil(t, quotaUsed)
|
|
})
|
|
|
|
// Upsert two secure variables, deletes one, then ensure the
|
|
// remaining is left as expected.
|
|
t.Run("2 upsert variable and delete", func(t *testing.T) {
|
|
|
|
ns := mock.Namespace()
|
|
ns.Name = svs[0].Namespace
|
|
require.NoError(t, testState.UpsertNamespaces(initialIndex, []*structs.Namespace{ns}))
|
|
|
|
initialIndex++
|
|
require.NoError(t, testState.UpsertSecureVariables(
|
|
structs.MsgTypeTestSetup, initialIndex, svs))
|
|
|
|
// Perform the delete.
|
|
delete1Index := uint64(20)
|
|
require.NoError(t, testState.DeleteSecureVariables(
|
|
structs.MsgTypeTestSetup, delete1Index, svs[0].Namespace, []string{svs[0].Path}))
|
|
|
|
// Check that the index for the table was modified as expected.
|
|
actualDelete1Index, err := testState.Index(TableSecureVariables)
|
|
require.NoError(t, err)
|
|
require.Equal(t, delete1Index, actualDelete1Index, "index should have changed")
|
|
|
|
ws := memdb.NewWatchSet()
|
|
|
|
// Get the secure variables from the table.
|
|
iter, err := testState.SecureVariables(ws)
|
|
require.NoError(t, err)
|
|
|
|
var delete1Count int
|
|
var expectedQuotaSize int
|
|
|
|
// Iterate all the stored variables and assert we have the expected
|
|
// number.
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
delete1Count++
|
|
v := raw.(*structs.SecureVariableEncrypted)
|
|
expectedQuotaSize += len(v.Data)
|
|
}
|
|
require.Equal(t, 1, delete1Count, "unexpected number of variables in table")
|
|
quotaUsed, err := testState.SecureVariablesQuotaByNamespace(ws, structs.DefaultNamespace)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(expectedQuotaSize), quotaUsed.Size)
|
|
})
|
|
|
|
t.Run("3 delete remaining variable", func(t *testing.T) {
|
|
delete2Index := uint64(30)
|
|
require.NoError(t, testState.DeleteSecureVariable(
|
|
delete2Index, svs[1].Namespace, svs[1].Path))
|
|
|
|
// Check that the index for the table was modified as expected.
|
|
actualDelete2Index, err := testState.Index(TableSecureVariables)
|
|
require.NoError(t, err)
|
|
require.Equal(t, delete2Index, actualDelete2Index, "index should have changed")
|
|
|
|
// Get the secure variables from the table.
|
|
ws := memdb.NewWatchSet()
|
|
iter, err := testState.SecureVariables(ws)
|
|
require.NoError(t, err)
|
|
|
|
var delete2Count int
|
|
|
|
// Ensure the table is empty.
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
delete2Count++
|
|
}
|
|
require.Equal(t, 0, delete2Count, "unexpected number of variables in table")
|
|
|
|
quotaUsed, err := testState.SecureVariablesQuotaByNamespace(ws, structs.DefaultNamespace)
|
|
require.NoError(t, err)
|
|
require.Equal(t, int64(0), quotaUsed.Size)
|
|
})
|
|
}
|
|
|
|
func TestStateStore_GetSecureVariables(t *testing.T) {
|
|
ci.Parallel(t)
|
|
testState := testStateStore(t)
|
|
|
|
ns := mock.Namespace()
|
|
ns.Name = "~*magical*~"
|
|
initialIndex := uint64(10)
|
|
require.NoError(t, testState.UpsertNamespaces(initialIndex, []*structs.Namespace{ns}))
|
|
|
|
// Generate some test secure variables and upsert them.
|
|
svs, _ := mockSecureVariables(2)
|
|
svs[0].Namespace = "~*magical*~"
|
|
initialIndex++
|
|
require.NoError(t, testState.UpsertSecureVariables(structs.MsgTypeTestSetup, initialIndex, svs))
|
|
|
|
// Look up secure variables using the namespace of the first mock variable.
|
|
ws := memdb.NewWatchSet()
|
|
iter, err := testState.GetSecureVariablesByNamespace(ws, svs[0].Namespace)
|
|
require.NoError(t, err)
|
|
|
|
var count1 int
|
|
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
count1++
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
t.Logf("- sv: n=%q p=%q ci=%v mi=%v ed.ki=%q", sv.Namespace, sv.Path, sv.CreateIndex, sv.ModifyIndex, sv.KeyID)
|
|
require.Equal(t, initialIndex, sv.CreateIndex, "incorrect create index", sv.Path)
|
|
require.Equal(t, initialIndex, sv.ModifyIndex, "incorrect modify index", sv.Path)
|
|
require.Equal(t, svs[0].Namespace, sv.Namespace)
|
|
}
|
|
require.Equal(t, 1, count1)
|
|
|
|
// Look up variables using the namespace of the second mock variable.
|
|
iter, err = testState.GetSecureVariablesByNamespace(ws, svs[1].Namespace)
|
|
require.NoError(t, err)
|
|
|
|
var count2 int
|
|
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
count2++
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
t.Logf("- sv: n=%q p=%q ci=%v mi=%v ed.ki=%q", sv.Namespace, sv.Path, sv.CreateIndex, sv.ModifyIndex, sv.KeyID)
|
|
require.Equal(t, initialIndex, sv.CreateIndex, "incorrect create index", sv.Path)
|
|
require.Equal(t, initialIndex, sv.ModifyIndex, "incorrect modify index", sv.Path)
|
|
require.Equal(t, svs[1].Namespace, sv.Namespace)
|
|
}
|
|
require.Equal(t, 1, count2)
|
|
|
|
// Look up variables using a namespace that shouldn't contain any
|
|
// variables.
|
|
iter, err = testState.GetSecureVariablesByNamespace(ws, "pony-club")
|
|
require.NoError(t, err)
|
|
|
|
var count3 int
|
|
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
count3++
|
|
}
|
|
require.Equal(t, 0, count3)
|
|
}
|
|
|
|
func TestStateStore_ListSecureVariablesByNamespaceAndPrefix(t *testing.T) {
|
|
ci.Parallel(t)
|
|
testState := testStateStore(t)
|
|
|
|
// Generate some test secure variables and upsert them.
|
|
svs, _ := mockSecureVariables(6)
|
|
svs[0].Path = "a/b"
|
|
svs[1].Path = "a/b/c"
|
|
svs[2].Path = "unrelated/b/c"
|
|
svs[3].Namespace = "other"
|
|
svs[3].Path = "a/b/c"
|
|
svs[4].Namespace = "other"
|
|
svs[4].Path = "a/q/z"
|
|
svs[5].Namespace = "other"
|
|
svs[5].Path = "a/z/z"
|
|
|
|
ns := mock.Namespace()
|
|
ns.Name = "other"
|
|
initialIndex := uint64(10)
|
|
require.NoError(t, testState.UpsertNamespaces(initialIndex, []*structs.Namespace{ns}))
|
|
|
|
initialIndex++
|
|
require.NoError(t, testState.UpsertSecureVariables(structs.MsgTypeTestSetup, initialIndex, svs))
|
|
|
|
t.Run("ByNamespace", func(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
namespace string
|
|
expectedCount int
|
|
}{
|
|
{
|
|
desc: "default",
|
|
namespace: "default",
|
|
expectedCount: 2,
|
|
},
|
|
{
|
|
desc: "other",
|
|
namespace: "other",
|
|
expectedCount: 3,
|
|
},
|
|
{
|
|
desc: "nonexistent",
|
|
namespace: "BAD",
|
|
expectedCount: 0,
|
|
},
|
|
}
|
|
|
|
ws := memdb.NewWatchSet()
|
|
for _, tC := range testCases {
|
|
t.Run(tC.desc, func(t *testing.T) {
|
|
iter, err := testState.GetSecureVariablesByNamespace(ws, tC.namespace)
|
|
require.NoError(t, err)
|
|
|
|
var count int = 0
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
count++
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
t.Logf("- sv: n=%q p=%q ci=%v mi=%v ed.ki=%q", sv.Namespace, sv.Path, sv.CreateIndex, sv.ModifyIndex, sv.KeyID)
|
|
require.Equal(t, initialIndex, sv.CreateIndex, "incorrect create index", sv.Path)
|
|
require.Equal(t, initialIndex, sv.ModifyIndex, "incorrect modify index", sv.Path)
|
|
require.Equal(t, tC.namespace, sv.Namespace)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("ByNamespaceAndPrefix", func(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
namespace string
|
|
prefix string
|
|
expectedCount int
|
|
}{
|
|
{
|
|
desc: "ns1 with good path",
|
|
namespace: "default",
|
|
prefix: "a",
|
|
expectedCount: 2,
|
|
},
|
|
{
|
|
desc: "ns2 with good path",
|
|
namespace: "other",
|
|
prefix: "a",
|
|
expectedCount: 3,
|
|
},
|
|
{
|
|
desc: "ns1 path valid for ns2",
|
|
namespace: "default",
|
|
prefix: "a/b/c",
|
|
expectedCount: 1,
|
|
},
|
|
{
|
|
desc: "ns2 empty prefix",
|
|
namespace: "other",
|
|
prefix: "",
|
|
expectedCount: 3,
|
|
},
|
|
{
|
|
desc: "nonexistent ns",
|
|
namespace: "BAD",
|
|
prefix: "",
|
|
expectedCount: 0,
|
|
},
|
|
}
|
|
|
|
ws := memdb.NewWatchSet()
|
|
for _, tC := range testCases {
|
|
t.Run(tC.desc, func(t *testing.T) {
|
|
iter, err := testState.GetSecureVariablesByNamespaceAndPrefix(ws, tC.namespace, tC.prefix)
|
|
require.NoError(t, err)
|
|
|
|
var count int = 0
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
count++
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
t.Logf("- sv: n=%q p=%q ci=%v mi=%v ed.ki=%q", sv.Namespace, sv.Path, sv.CreateIndex, sv.ModifyIndex, sv.KeyID)
|
|
require.Equal(t, initialIndex, sv.CreateIndex, "incorrect create index", sv.Path)
|
|
require.Equal(t, initialIndex, sv.ModifyIndex, "incorrect modify index", sv.Path)
|
|
require.Equal(t, tC.namespace, sv.Namespace)
|
|
require.True(t, strings.HasPrefix(sv.Path, tC.prefix))
|
|
}
|
|
require.Equal(t, tC.expectedCount, count)
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("ByPrefix", func(t *testing.T) {
|
|
testCases := []struct {
|
|
desc string
|
|
prefix string
|
|
expectedCount int
|
|
}{
|
|
{
|
|
desc: "bad prefix",
|
|
prefix: "bad",
|
|
expectedCount: 0,
|
|
},
|
|
{
|
|
desc: "multiple ns",
|
|
prefix: "a/b/c",
|
|
expectedCount: 2,
|
|
},
|
|
{
|
|
desc: "all",
|
|
prefix: "",
|
|
expectedCount: 6,
|
|
},
|
|
}
|
|
|
|
ws := memdb.NewWatchSet()
|
|
for _, tC := range testCases {
|
|
t.Run(tC.desc, func(t *testing.T) {
|
|
iter, err := testState.GetSecureVariablesByPrefix(ws, tC.prefix)
|
|
require.NoError(t, err)
|
|
|
|
var count int = 0
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
count++
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
t.Logf("- sv: n=%q p=%q ci=%v mi=%v ed.ki=%q", sv.Namespace, sv.Path, sv.CreateIndex, sv.ModifyIndex, sv.KeyID)
|
|
require.Equal(t, initialIndex, sv.CreateIndex, "incorrect create index", sv.Path)
|
|
require.Equal(t, initialIndex, sv.ModifyIndex, "incorrect modify index", sv.Path)
|
|
require.True(t, strings.HasPrefix(sv.Path, tC.prefix))
|
|
}
|
|
require.Equal(t, tC.expectedCount, count)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
func TestStateStore_ListSecureVariablesByKeyID(t *testing.T) {
|
|
ci.Parallel(t)
|
|
testState := testStateStore(t)
|
|
|
|
// Generate some test secure variables and upsert them.
|
|
svs, _ := mockSecureVariables(7)
|
|
keyID := uuid.Generate()
|
|
|
|
expectedForKey := []string{}
|
|
for i := 0; i < 5; i++ {
|
|
svs[i].KeyID = keyID
|
|
expectedForKey = append(expectedForKey, svs[i].Path)
|
|
sort.Strings(expectedForKey)
|
|
}
|
|
|
|
expectedOrphaned := []string{svs[5].Path, svs[6].Path}
|
|
|
|
initialIndex := uint64(10)
|
|
require.NoError(t, testState.UpsertSecureVariables(
|
|
structs.MsgTypeTestSetup, initialIndex, svs))
|
|
|
|
ws := memdb.NewWatchSet()
|
|
iter, err := testState.GetSecureVariablesByKeyID(ws, keyID)
|
|
require.NoError(t, err)
|
|
|
|
var count int
|
|
for raw := iter.Next(); raw != nil; raw = iter.Next() {
|
|
sv := raw.(*structs.SecureVariableEncrypted)
|
|
require.Equal(t, keyID, sv.KeyID)
|
|
require.Equal(t, expectedForKey[count], sv.Path)
|
|
require.NotContains(t, expectedOrphaned, sv.Path)
|
|
count++
|
|
}
|
|
require.Equal(t, 5, count)
|
|
}
|
|
|
|
// mockSecureVariables returns a random number of secure variables between min
|
|
// and max inclusive.
|
|
func mockSecureVariables(count int) (
|
|
[]*structs.SecureVariableEncrypted, secureVariableMocks) {
|
|
var svm secureVariableMocks = make(map[string]*structs.SecureVariableEncrypted, count)
|
|
for i := 0; i < count; i++ {
|
|
nv := mock.SecureVariableEncrypted()
|
|
// There is an extremely rare chance of path collision because the mock
|
|
// secure variables generate their paths randomly. This check will add
|
|
// an extra component on conflict to (ideally) disambiguate them.
|
|
if _, found := svm[nv.Path]; found {
|
|
nv.Path = nv.Path + "/" + fmt.Sprint(time.Now().UnixNano())
|
|
}
|
|
svm[nv.Path] = nv
|
|
}
|
|
return svm.List(), svm
|
|
}
|
|
|
|
type secureVariableMocks map[string]*structs.SecureVariableEncrypted
|
|
|
|
func (svm secureVariableMocks) List() []*structs.SecureVariableEncrypted {
|
|
out := make([]*structs.SecureVariableEncrypted, len(svm))
|
|
i := 0
|
|
for _, v := range svm {
|
|
out[i] = v
|
|
i++
|
|
}
|
|
// objects will always come out of state store in namespace, path order.
|
|
sort.SliceStable(out, func(i, j int) bool {
|
|
if out[i].Namespace != out[j].Namespace {
|
|
return out[i].Namespace < out[j].Namespace
|
|
}
|
|
return out[i].Path < out[j].Path
|
|
})
|
|
return out
|
|
}
|
|
|
|
func printSecureVariable(tsv *structs.SecureVariableEncrypted) string {
|
|
b, _ := json.Marshal(tsv)
|
|
return string(b)
|
|
}
|
|
|
|
func printSecureVariables(tsvs []*structs.SecureVariableEncrypted) string {
|
|
if len(tsvs) == 0 {
|
|
return ""
|
|
}
|
|
var out strings.Builder
|
|
for _, tsv := range tsvs {
|
|
out.WriteString(printSecureVariable(tsv) + "\n")
|
|
}
|
|
return out.String()
|
|
}
|