mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 02:57:59 +00:00
[VAULT-31754] Check removed status in sys/unseal and error out if the node has been removed from the cluster (#28909)
This commit is contained in:
@@ -85,6 +85,14 @@ func handleSysUnseal(core *vault.Core) http.Handler {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if this node was removed from the cluster. If so, respond with an error and return,
|
||||||
|
// since we don't want a removed node to be able to unseal.
|
||||||
|
removed, ok := core.IsRemovedFromCluster()
|
||||||
|
if ok && removed {
|
||||||
|
respondError(w, http.StatusInternalServerError, errors.New("node was removed from a HA cluster"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Parse the request
|
// Parse the request
|
||||||
var req UnsealRequest
|
var req UnsealRequest
|
||||||
if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil {
|
if _, err := parseJSONRequest(core.PerfStandby(), r, w, &req); err != nil {
|
||||||
|
|||||||
@@ -439,6 +439,35 @@ func TestSysUnseal_Reset(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSysUnseal_NodeRemovedFromCluster verifies that a call to /sys/unseal fails
|
||||||
|
// with an appropriate error when the node has been removed from a cluster.
|
||||||
|
func TestSysUnseal_NodeRemovedFromCluster(t *testing.T) {
|
||||||
|
core, err := vault.TestCoreWithMockRemovableNodeHABackend(t, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
ln, addr := TestServer(t, core)
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
// The value of key doesn't matter here, we just need to make a request.
|
||||||
|
resp := testHttpPut(t, "", addr+"/v1/sys/unseal", map[string]interface{}{
|
||||||
|
"key": "foo",
|
||||||
|
})
|
||||||
|
|
||||||
|
testResponseStatus(t, resp, 500)
|
||||||
|
var actual map[string]interface{}
|
||||||
|
testResponseBody(t, resp, &actual)
|
||||||
|
|
||||||
|
errors, ok := actual["errors"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("no errors in the response, request should be invalid")
|
||||||
|
}
|
||||||
|
expectedErrorMsg := "node was removed from a HA cluster"
|
||||||
|
if !strings.Contains(errors[0].(string), expectedErrorMsg) {
|
||||||
|
t.Fatalf("error message should contain %q", expectedErrorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Test Seal's permissions logic, which is slightly different than normal code
|
// Test Seal's permissions logic, which is slightly different than normal code
|
||||||
// paths in that it queries the ACL rather than having checkToken do it. This
|
// paths in that it queries the ACL rather than having checkToken do it. This
|
||||||
// is because it was abusing RootPaths in logical_system, but that caused some
|
// is because it was abusing RootPaths in logical_system, but that caused some
|
||||||
|
|||||||
@@ -4573,3 +4573,24 @@ func (c *Core) setupAuditedHeadersConfig(ctx context.Context) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsRemovedFromCluster checks whether this node has been removed from the
|
||||||
|
// cluster. This is only applicable to physical HA backends that satisfy the
|
||||||
|
// RemovableNodeHABackend interface. The value of the `ok` result will be false
|
||||||
|
// if the HA and underlyingPhysical backends are nil or do not support this operation.
|
||||||
|
func (c *Core) IsRemovedFromCluster() (removed, ok bool) {
|
||||||
|
var haBackend any
|
||||||
|
if c.ha != nil {
|
||||||
|
haBackend = c.ha
|
||||||
|
} else if c.underlyingPhysical != nil {
|
||||||
|
haBackend = c.underlyingPhysical
|
||||||
|
} else {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
removableNodeHA, ok := haBackend.(physical.RemovableNodeHABackend)
|
||||||
|
if !ok {
|
||||||
|
return false, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return removableNodeHA.IsRemoved(), true
|
||||||
|
}
|
||||||
|
|||||||
@@ -3700,3 +3700,59 @@ func TestBarrier_DeadlockDetection(t *testing.T) {
|
|||||||
t.Fatal("barrierLock doesn't have deadlock detection enabled, it should")
|
t.Fatal("barrierLock doesn't have deadlock detection enabled, it should")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestCore_IsRemovedFromCluster exercises all the execution paths in the
|
||||||
|
// IsRemovedFromCluster convenience method of the Core struct.
|
||||||
|
func TestCore_IsRemovedFromCluster(t *testing.T) {
|
||||||
|
core := &Core{}
|
||||||
|
|
||||||
|
// Test case where both HA and underlying physical backends ares nil
|
||||||
|
removed, ok := core.IsRemovedFromCluster()
|
||||||
|
if removed || ok {
|
||||||
|
t.Fatalf("expected removed and ok to be false, got removed: %v, ok: %v", removed, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test case where HA backend is nil, but the underlying physical is there and does not support RemovableNodeHABackend
|
||||||
|
core.underlyingPhysical = &MockHABackend{}
|
||||||
|
removed, ok = core.IsRemovedFromCluster()
|
||||||
|
if removed || ok {
|
||||||
|
t.Fatalf("expected removed and ok to be false, got removed: %v, ok: %v", removed, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test case where HA backend is nil, but the underlying physical is there, supports RemovableNodeHABackend, and is not removed
|
||||||
|
mockHA := &MockRemovableNodeHABackend{}
|
||||||
|
core.underlyingPhysical = mockHA
|
||||||
|
removed, ok = core.IsRemovedFromCluster()
|
||||||
|
if removed || !ok {
|
||||||
|
t.Fatalf("expected removed and ok to be false, got removed: %v, ok: %v", removed, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test case where HA backend is nil, but the underlying physical is there, supports RemovableNodeHABackend, and is removed
|
||||||
|
mockHA.Removed = true
|
||||||
|
removed, ok = core.IsRemovedFromCluster()
|
||||||
|
if !removed || !ok {
|
||||||
|
t.Fatalf("expected removed to be false and ok to be true, got removed: %v, ok: %v", removed, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test case where HA backend does not support RemovableNodeHABackend
|
||||||
|
core.ha = &MockHABackend{}
|
||||||
|
removed, ok = core.IsRemovedFromCluster()
|
||||||
|
if removed || ok {
|
||||||
|
t.Fatalf("expected removed and ok to be false, got removed: %v, ok: %v", removed, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test case where HA backend supports RemovableNodeHABackend and is not removed
|
||||||
|
mockHA.Removed = false
|
||||||
|
core.ha = mockHA
|
||||||
|
removed, ok = core.IsRemovedFromCluster()
|
||||||
|
if removed || !ok {
|
||||||
|
t.Fatalf("expected removed and ok to be true, got removed: %v, ok: %v", removed, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test case where HA backend supports RemovableNodeHABackend and is removed
|
||||||
|
mockHA.Removed = true
|
||||||
|
removed, ok = core.IsRemovedFromCluster()
|
||||||
|
if !removed || !ok {
|
||||||
|
t.Fatalf("expected removed to be false and ok to be true, got removed: %v, ok: %v", removed, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2315,3 +2315,44 @@ func TestCreateStorageGroup(ctx context.Context, c *Core, entityIDs []string) er
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mock HABackend is a non-functional HABackend for testing purposes
|
||||||
|
type MockHABackend struct {
|
||||||
|
physical.HABackend
|
||||||
|
physical.Backend
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHABackend) HAEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockRemovableNodeHABackend is a barely functional RemovableNodeHABackend for testing purposes.
|
||||||
|
// It has a functional IsRemoved method and an exported Removed field so that the desired state can be easily set.
|
||||||
|
type MockRemovableNodeHABackend struct {
|
||||||
|
physical.RemovableNodeHABackend
|
||||||
|
physical.Backend
|
||||||
|
Removed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRemovableNodeHABackend) HAEnabled() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockRemovableNodeHABackend) IsRemoved() bool {
|
||||||
|
return m.Removed
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoreWithMockRemovableNodeHABackend(t *testing.T, removed bool) (*Core, error) {
|
||||||
|
t.Helper()
|
||||||
|
logger := corehelpers.NewTestLogger(t)
|
||||||
|
inmha, err := physInmem.NewInmemHA(nil, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
conf := testCoreConfig(t, inmha, logger)
|
||||||
|
mockHABackend := &MockRemovableNodeHABackend{Removed: removed}
|
||||||
|
conf.HAPhysical = mockHABackend
|
||||||
|
conf.RedirectAddr = "http://127.0.0.1:8200"
|
||||||
|
|
||||||
|
return NewCore(conf)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user