mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 18:17:55 +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
|
||||
}
|
||||
|
||||
// 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
|
||||
var req UnsealRequest
|
||||
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
|
||||
// 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
|
||||
|
||||
@@ -4573,3 +4573,24 @@ func (c *Core) setupAuditedHeadersConfig(ctx context.Context) error {
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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