Skip to content

Cluster Security

Configure authentication and security for NornicDB clusters.

Overview

NornicDB clusters use the same authentication system for both client connections and inter-node communication:

  • Bolt Protocol Authentication - Standard Neo4j-compatible auth (port 7687)
  • JWT Bearer Tokens - Recommended for cluster inter-node auth
  • Service Accounts - Non-human identities for automation
  • RBAC Integration - Full role-based access control

Supported Auth Schemes

Scheme Principal Description
basic username Username/password authentication
basic (empty) JWT token in credentials field
bearer (ignored) JWT token in credentials field
none - Anonymous (if enabled, grants viewer role)

Authentication Methods

Basic Authentication

Username/password authentication via Bolt protocol:

// Go
driver, _ := neo4j.NewDriverWithContext(
    "bolt://localhost:7687",
    neo4j.BasicAuth("admin", "password", ""),
)
# Python
driver = GraphDatabase.driver(
    "bolt://localhost:7687",
    auth=("admin", "password")
)
// JavaScript/TypeScript
const driver = neo4j.driver(
    'bolt://localhost:7687',
    neo4j.auth.basic('admin', 'password')
);

For cluster nodes, JWT tokens provide stateless, scalable authentication:

Step 1: Generate a Shared JWT Secret

# Generate a 48-byte (384-bit) secret (min 32 bytes required)
openssl rand -base64 48
# Example: K7xB2mN9pQr4sT6uV8wY0zA3bC5dE7fG9hI1jK3lM5nO7pQ9rS1tU3vW5xY7zA==

⚠️ Important: This secret must be identical on ALL cluster nodes.

Step 2: Configure All Nodes

# .env file for ALL cluster nodes
NORNICDB_JWT_SECRET=K7xB2mN9pQr4sT6uV8wY0zA3bC5dE7fG9hI1jK3lM5nO7pQ9rS1tU3vW5xY7zA==
NORNICDB_BOLT_REQUIRE_AUTH=true

Step 3: Generate Cluster Tokens

Via API:

curl -X POST http://localhost:7474/api/v1/auth/cluster-token \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"node_id": "node-2", "role": "admin"}'

Via Go code:

// Token never expires (default)
token, _ := authenticator.GenerateClusterToken("node-2", auth.RoleAdmin)

// Token with custom expiry
token7d, _ := authenticator.GenerateClusterTokenWithExpiry(
    "node-2", auth.RoleAdmin, 7*24*time.Hour)

Step 4: Connect Using JWT

// Go - Empty username signals JWT authentication
driver, _ := neo4j.NewDriverWithContext(
    "bolt://node1.cluster.local:7687",
    neo4j.BasicAuth("", token, ""),  // Empty username = JWT
)
# Python
driver = GraphDatabase.driver(
    "bolt://node1.cluster.local:7687",
    auth=("", token)  # Empty username = JWT
)
// JavaScript/TypeScript
const driver = neo4j.driver(
    'bolt://node1.cluster.local:7687',
    neo4j.auth.basic('', token)  // Empty username = JWT
);

Cluster Node Authentication

Service Account Setup

Create service accounts for cluster nodes:

# Via HTTP API (port 7474 for HTTP, 7687 for Bolt)
curl -X POST http://localhost:7474/api/v1/service-accounts \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "cluster-node-1",
    "secret": "secure-generated-password",
    "role": "admin"
  }'

Via Go code:

// Create service account for cluster node
authenticator.CreateUser("cluster-node-1", "secure-password", 
    []auth.Role{auth.RoleAdmin})

Docker Compose with Auth

version: '3.8'

services:
  nornicdb-node1:
    image: nornicdb:latest
    environment:
      NORNICDB_JWT_SECRET: ${SHARED_JWT_SECRET}
      NORNICDB_BOLT_REQUIRE_AUTH: "true"
      NORNICDB_BOLT_ALLOW_ANONYMOUS: "false"
    secrets:
      - jwt-secret

secrets:
  jwt-secret:
    external: true

Permissions

Query Type Required Permission
MATCH ... RETURN read
CREATE, MERGE, SET write
DELETE delete
CREATE INDEX schema

Security Best Practices

1. Use Strong Secrets

# Generate secure secrets
openssl rand -base64 32  # Service account
openssl rand -base64 48  # JWT secret

2. Network Isolation

# Isolate cluster network
networks:
  cluster-internal:
    internal: true  # No external access

3. Secret Rotation

Rotate secrets quarterly:

func RotateServiceAccountSecret(auth *Authenticator, accountID string) (string, error) {
    newSecret := generateSecureSecret()
    err := auth.UpdateServiceAccountSecret(accountID, newSecret)
    return newSecret, err
}

Troubleshooting

Authentication Failures

# Check service account exists (HTTP API port 7474)
curl -X GET http://localhost:7474/api/v1/service-accounts/cluster-node-1 \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Permission Denied

# Check/update account role
curl -X PATCH http://localhost:7474/api/v1/service-accounts/cluster-node-1 \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"role": "admin"}'

Debug Logging

export NORNICDB_LOG_LEVEL=debug
export NORNICDB_AUTH_DEBUG=true

See Also