Skip to content

Commit 9d15b40

Browse files
authored
block deleting namespace if it contains a secure variable (#13888)
When we delete a namespace, we check to ensure that there are no non-terminal jobs or CSI volume, which also covers evals, allocs, etc. Secure variables are also namespaces, so extend this check to them as well.
1 parent 32a3857 commit 9d15b40

File tree

3 files changed

+48
-0
lines changed

3 files changed

+48
-0
lines changed

nomad/state/state_store.go

+11
Original file line numberDiff line numberDiff line change
@@ -6352,6 +6352,17 @@ func (s *StateStore) DeleteNamespaces(index uint64, names []string) error {
63526352
"All CSI volumes in namespace must be deleted before it can be deleted", name, vol.ID)
63536353
}
63546354

6355+
varIter, err := s.getSecureVariablesByNamespaceImpl(txn, nil, name)
6356+
if err != nil {
6357+
return err
6358+
}
6359+
if varIter.Next() != nil {
6360+
// unlike job/volume, don't show the path here because the user may
6361+
// not have List permissions on the secure vars in this namespace
6362+
return fmt.Errorf("namespace %q contains at least one secure variable. "+
6363+
"All secure variables in namespace must be deleted before it can be deleted", name)
6364+
}
6365+
63556366
// Delete the namespace
63566367
if err := txn.Delete(TableNamespaces, existing); err != nil {
63576368
return fmt.Errorf("namespace deletion failed: %v", err)

nomad/state/state_store_secure_variables.go

+4
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@ func (s *StateStore) SecureVariables(ws memdb.WatchSet) (memdb.ResultIterator, e
2929
func (s *StateStore) GetSecureVariablesByNamespace(
3030
ws memdb.WatchSet, namespace string) (memdb.ResultIterator, error) {
3131
txn := s.db.ReadTxn()
32+
return s.getSecureVariablesByNamespaceImpl(txn, ws, namespace)
33+
}
3234

35+
func (s *StateStore) getSecureVariablesByNamespaceImpl(
36+
txn *txn, ws memdb.WatchSet, namespace string) (memdb.ResultIterator, error) {
3337
// Walk the entire table.
3438
iter, err := txn.Get(TableSecureVariables, indexID+"_prefix", namespace, "")
3539
if err != nil {

nomad/state/state_store_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,39 @@ func TestStateStore_DeleteNamespaces_CSIVolumes(t *testing.T) {
10441044
require.False(t, watchFired(ws))
10451045
}
10461046

1047+
func TestStateStore_DeleteNamespaces_SecureVariables(t *testing.T) {
1048+
ci.Parallel(t)
1049+
1050+
state := testStateStore(t)
1051+
1052+
ns := mock.Namespace()
1053+
require.NoError(t, state.UpsertNamespaces(1000, []*structs.Namespace{ns}))
1054+
1055+
sv := mock.SecureVariableEncrypted()
1056+
sv.Namespace = ns.Name
1057+
require.NoError(t, state.UpsertSecureVariables(structs.MsgTypeTestSetup, 1001, []*structs.SecureVariableEncrypted{sv}))
1058+
1059+
// Create a watchset so we can test that delete fires the watch
1060+
ws := memdb.NewWatchSet()
1061+
_, err := state.NamespaceByName(ws, ns.Name)
1062+
require.NoError(t, err)
1063+
1064+
err = state.DeleteNamespaces(1002, []string{ns.Name})
1065+
require.Error(t, err)
1066+
require.Contains(t, err.Error(), "one secure variable")
1067+
require.False(t, watchFired(ws))
1068+
1069+
ws = memdb.NewWatchSet()
1070+
out, err := state.NamespaceByName(ws, ns.Name)
1071+
require.NoError(t, err)
1072+
require.NotNil(t, out)
1073+
1074+
index, err := state.Index(TableNamespaces)
1075+
require.NoError(t, err)
1076+
require.EqualValues(t, 1000, index)
1077+
require.False(t, watchFired(ws))
1078+
}
1079+
10471080
func TestStateStore_Namespaces(t *testing.T) {
10481081
ci.Parallel(t)
10491082

0 commit comments

Comments
 (0)