Skip to content

Bolt protocol: writes to a follower in Raft HA fail with "Cannot forward command to leader: no authenticated user in the current security context" #4456

@robfrank

Description

@robfrank

Summary

In a Raft HA cluster, write queries (CREATE, MERGE, etc.) sent over the Bolt protocol succeed only when the load balancer happens to route the connection to the current leader pod. When the connection lands on a follower, the client receives:

{neo4j_code: Neo.DatabaseError.General.UnknownError}
{message: Cannot forward command to leader: no authenticated user in the current security context}

The REST/HTTP endpoint is unaffected. Reported against an EKS deployment using the Helm chart.

Root cause

The Bolt connection handler never binds the authenticated user onto the thread-local DatabaseContext.

BoltNetworkExecutor.ensureDatabase() resolves the database via server.getDatabase(targetName) and returns it, but never calls DatabaseContext.INSTANCE.init(...).setCurrentUser(...). The Bolt module has zero references to DatabaseContext / setCurrentUser (verified across bolt/src/main/java).

On a write to a follower, RaftReplicatedDatabase.command() forwards the statement to the leader via forwardCommandToLeaderViaRaft(...), which reads the current user to set the X-ArcadeDB-Forwarded-User header:

// ha-raft/.../RaftReplicatedDatabase.java:1523
final String proxiedUser = proxied.getCurrentUserName();
if (proxiedUser == null)
  throw new SecurityException(
      "Cannot forward command to leader: no authenticated user in the current security context"); // :1526
builder.header("X-ArcadeDB-Forwarded-User", proxiedUser);                                          // :1527

Because the current user was never bound to the context, getCurrentUserName() returns null and the forward throws. The Bolt driver surfaces it as Neo.DatabaseError.General.UnknownError.

Why REST and Postgres work but Bolt does not

  • HTTP: DatabaseAbstractHandler binds the user on the DatabaseContext before executing.
  • Postgres: PostgresNetworkExecutor.openDatabase() does DatabaseContext.INSTANCE.init((DatabaseInternal) database).setCurrentUser(dbUser.getDatabaseUser(database)).
  • gRPC: previously had the identical bug; fixed by mirroring the Postgres pattern (regression test GrpcFollowerForwardingIT). The fix recorded explicit guidance that any new wire-protocol module (Bolt, MongoDB, Redis) must set the current user on DatabaseContext after init. Bolt (and, as noted below, MongoDB and Redis) never received that treatment.

Affected components

  • bolt/src/main/java/com/arcadedb/bolt/BoltNetworkExecutor.java - ensureDatabase() does not bind the authenticated ServerSecurityUser user (already held as a field) onto the DatabaseContext.
  • Likely the same defect (no DatabaseContext/setCurrentUser binding) in mongodbw and redisw - to be audited as part of the fix. These use different write paths, so whether they hit the same .command() forwarding path needs investigation; follow-up issues may be filed if their failure mode differs.

Proposed fix

Mirror the Postgres/gRPC pattern in BoltNetworkExecutor.ensureDatabase(): after resolving / switching the database, bind the already-authenticated user:

DatabaseContext.INSTANCE.init((DatabaseInternal) database).setCurrentUser(user.getDatabaseUser(database));

This must run whenever the database is opened or switched (the ensureDatabase() switch path), and the Bolt executor runs on a dedicated per-connection thread, so the thread-local binding persists for the connection lifetime.

Steps to reproduce

  1. Start a 3-node ArcadeDB Raft HA cluster.
  2. Connect a Neo4j/Bolt driver to a follower node.
  3. Run a write query, e.g. CREATE (n:Person {name:'x'}).
  4. Observe the Neo.DatabaseError.General.UnknownError "no authenticated user in the current security context" failure. The same query against the leader succeeds.

Proposed regression coverage

  • Unit test asserting ensureDatabase() binds the current user on DatabaseContext.
  • BoltFollowerForwardingIT mirroring GrpcFollowerForwardingIT: 3-node Raft cluster (BaseRaftHATest), Neo4j driver writing to a follower, asserting the write succeeds and replicates to all nodes. (Requires adding arcadedb-ha-raft test-scoped deps to bolt/pom.xml, as grpcw/pom.xml already does.)

Metadata

Metadata

Assignees

Labels

Type

No fields configured for Bug.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions