Skip to content

Latest commit

 

History

142 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

NATS Consol

OpenSSF Scorecard

Open-source, self-hosted admin console for NATS JetStreamgithub.com/gopherust-io/nats-console.

Manage streams, consumers, browse messages, tail live traffic, manage KV/Object stores, and monitor multi-cluster deployments from a modern web UI — without exposing NATS monitoring ports to the public internet.

Latest stable release: v0.11.0 · all releases

Latest stable release: see GitHub Releases.

Quick links: Architecture · Getting started · User guide · DevOps setup · Security


📖 Documentation: friendly guides for everyone — docs/README.md

Features (v0.8)

  • Multi-cluster registry with PostgreSQL persistence
  • Multi-tenant RBAC — operator/viewer/admin scoped by accessRules.clusterIds
  • Slow consumer detection — pending/lag/ack-pending thresholds, UI badges, Topology warnings, alert metrics
  • Incident Capsule Studio — capture/list/load forensic packs via gopherust-io/nats Incidents() (Object Store + KV index); DLQ “Capture capsule” + consumer panel with dry-run replay. Distinct from Audit incident reconstruction (Postgres timeline annotations).
  • Historical metrics — Postgres snapshots + Dashboard trend charts
  • Message publish — publish to JetStream streams from UI and API
  • Encryption key rotation — root API to re-encrypt stored credentials
  • Enterprise security — AES-GCM credential encryption, audit log, username/password auth and invite links, hardened pprof
  • Dashboard with JetStream account usage, server info, and jsz metrics
  • Stream list, create, update, delete, purge
  • Consumer CRUD with detail pages
  • Message browser with prev/next navigation and JSON/raw view
  • Live mode — real-time WebSocket tail per stream (race-safe hub)
  • KV Store and Object Store management
  • On-demand profiling — admin-only /api/v1/pprof/* (raw /debug/pprof disabled in production)
  • OTLP process telemetry via tel (HTTP/NATS counters); JetStream history via /api/v1/clusters/{id}/metrics/history
  • Helm chart for Kubernetes deployment
  • OpenAPI spec served at /api/openapi.yaml
  • Docker Compose quickstart with NATS + PostgreSQL + JetStream

Quick Start

git clone https://github.com/gopherust-io/nats-console.git
cd nats-console
cp .env.example .env   # set ADMIN_PASSWORD and other secrets
docker compose up --build

Open http://localhost:8080 and sign in:

  • Basic auth: admin / admin (or your .env ADMIN_* values)
  • People invite: Admin → People → create invite link → /invite/<token> sets password

For production, terminate TLS on an external reverse proxy or Ingress in front of :8080 (see devops-setup.md).

NATS is exposed locally on:

  • Client: nats://localhost:4222
  • Monitoring: http://localhost:8222

Local JetStream labs (single / 5-node cluster / supercluster / auth): docker/nats/ — see docs/local-docker.md. For the Replicas page use make nats-cluster-up. Stop the compose nats service first when ports clash.

PostgreSQL: set DATABASE_URL in .env (see .env.example). Stock compose is a local plaintext lab only.

On first startup, a default cluster is seeded from NATS_URL / NATS_MONITORING_URL (and optional NATS_TOKEN / NATS_CREDS_FILE) when the registry is empty.

Demo path (5 minutes)

  1. Start with docker compose up --build.
  2. Open http://localhost:8080 and sign in as admin.
  3. Create a stream, publish a test message, then open Live mode.
  4. Verify dashboard counters and stream/consumer updates refresh immediately.

If you want visuals in the repo page, add screenshots to docs/images/ and link them from this section.

Architecture

Browser → NATS Consol fasthttp (:8080) → PostgreSQL
                                       → NATS JetStream (4222)
                                       → NATS Monitoring (8222)
flowchart LR
    Browser[BrowserUI] --> Api[NatsConsolAPI]
    Api --> Pg[PostgreSQL]
    Api --> Js[NatsJetStream]
    Api --> Mon[NatsMonitoring]
Loading

The UI never talks to NATS directly. The backend acts as a secure gateway. All JetStream operations are cluster-scoped:

/api/v1/clusters/{clusterId}/streams
/api/v1/clusters/{clusterId}/live/ws
/api/v1/clusters/{clusterId}/kv/buckets
/api/v1/clusters/{clusterId}/objects/buckets

Production readiness checklist

  • ENV=production
  • Strong ADMIN_PASSWORD, ENCRYPTION_KEY, and RSA session key pair (SESSION_PRIVATE_KEY / SESSION_PUBLIC_KEY)
  • TLS configured end-to-end (https:// base URL and secure NATS/monitoring endpoints)
  • PostgreSQL with backups, retention, and restricted network access
  • Audit log retention and alerting on auth failures / critical mutations
  • CI + regression suites passing before deployment

Local Development

Requirements:

  • Go 1.26+
  • Node.js 22+
  • PostgreSQL 16+
  • NATS Server with JetStream enabled

Start dependencies:

cp .env.example .env   # required — set passwords and secrets
docker compose up postgres nats -d

Backend:

# Prefer values from .env; for host-run against published ports:
export DATABASE_URL=postgres://natsconsol:${POSTGRES_PASSWORD}@localhost:5432/natsconsol?sslmode=disable
export NATS_URL=nats://localhost:4222
export NATS_MONITORING_URL=http://localhost:8222
export ADMIN_PASSWORD=change-me-local-admin
export ENCRYPTION_KEY=dev-encryption-key-min-16-chars
# Generate once: openssl genrsa -out session.pem 2048 && openssl rsa -in session.pem -pubout -out session.pub.pem
# Then set SESSION_PRIVATE_KEY / SESSION_PUBLIC_KEY (PEM; literal \n escapes OK)
export SESSION_PRIVATE_KEY="$(awk 'NF {sub(/\r/,""); printf "%s\\n",$0}' session.pem)"
export SESSION_PUBLIC_KEY="$(awk 'NF {sub(/\r/,""); printf "%s\\n",$0}' session.pub.pem)"
go generate ./...      # after changing internal/config/config.go
go run ./cmd

Config is loaded via gopherust-io/env (envgen codegen). Install the generator once:

go install github.com/gopherust-io/env/cmd/envgen@latest

Frontend:

cd web
npm install
npm run dev

Frontend dev server: http://localhost:8080 (proxies /api to console on :8081 when using make dev-web-docker; local make dev-web expects the Go API on :8081)

Linting

Backend (requires golangci-lint v2+):

make lint-go
make lint-go-fix   # auto-fix (modernize, fieldalignment, tagalign, etc.)

Enabled Go linters include modernize, govet/fieldalignment (struct layout), errorlint, gosec, exptostd, intrange, perfsprint, tagalign, embeddedstructfieldcheck, and the standard set (staticcheck, errcheck, unused, …). Config: .golangci.yml.

Frontend (uses local npm when available, otherwise Docker):

make lint-web
# or explicitly via Docker:
make lint-web-docker

Both:

make lint

Configuration

Variable Default Description
HTTP_ADDR :8080 Console HTTP listen address
HTTP_READ_TIMEOUT 10s HTTP server read timeout
HTTP_WRITE_TIMEOUT 30s HTTP server write timeout
HTTP_IDLE_TIMEOUT 60s HTTP server idle timeout
NATS_TOKEN NATS auth token for default cluster bootstrap
NATS_ACCOUNT_SEED Optional account NKey seed for minting NATS user JWTs
PUBLIC_BASE_URL http://localhost:8080 Public base URL for invite links
ENV development Set to production to enforce TLS/auth secrets
DATABASE_URL — (required) PostgreSQL DSN; production requires sslmode=require|verify-ca|verify-full
DB_MAX_CONNS 25 PostgreSQL connection pool max size
DB_MIN_CONNS 2 PostgreSQL connection pool min size
DB_MAX_CONN_LIFETIME 1h Max lifetime of a pooled connection
DB_MAX_CONN_IDLE_TIME 30m Max idle time before a connection is closed
DB_HEALTH_CHECK_PERIOD 1m Interval between pool health checks
ENCRYPTION_KEY AES-GCM key for cluster tokens (required)
SESSION_PRIVATE_KEY PEM RSA private key (≥2048-bit) for RS256 session JWTs (required)
SESSION_PUBLIC_KEY PEM RSA public key matching the private key (required)
SESSION_TTL 15m Access JWT / session cookie lifetime
REFRESH_TOKEN_TTL 168h Opaque refresh cookie lifetime (bound to User-Agent + client IP fingerprint)
DEFAULT_CLUSTER_NAME default Name for env-seeded default cluster
NATS_URL NATS client URL for default cluster seed; production requires tls:// or wss://
NATS_CLIENT_CACHE_TTL 5m How long to cache NATS client connections per cluster
NATS_MONITORING_URL NATS monitoring URL; production requires https://
NATS_CREDS_FILE NATS credentials file (required in production if no token)
NATS_TOKEN NATS auth token (required in production if no creds file)
NATS_TLS_CA_FILE PEM CA file for NATS TLS verification
NATS_TLS_CERT_FILE PEM client certificate for NATS mTLS
NATS_TLS_KEY_FILE PEM client key for NATS mTLS (with cert)
NATS_TLS_SERVER_NAME Override TLS server name for NATS
NATS_TLS_INSECURE_SKIP_VERIFY false Skip NATS cert verify (forbidden in production)
STATIC_DIR Path to built frontend (web/dist)
ADMIN_USERNAME admin Bootstrap admin username
ADMIN_PASSWORD — (required) Bootstrap admin password (must not be admin in production)
CORS_ALLOWED_ORIGINS Comma-separated allowed origins
LOG_JSON false true for JSON logs (prod); false for console (local)
LOG_LEVEL info Log level: trace, debug, info, warn, error, fatal
TEL_COLLECTOR_GRPC_ADDR 127.0.0.1:4317 OTLP/gRPC collector address for process metrics/traces (github.com/gopherust-io/tel)
PPROF_ENABLED false Enable Go pprof on-demand endpoints (admin)
PPROF_AUTH_ENABLED true Require admin auth for pprof
PPROF_CPU_MAX_SECONDS 120 Max CPU profile duration
SMTP_ENABLED false Email console users when an alert first opens
SMTP_HOST / SMTP_PORT / SMTP_FROM — / 587 / — SMTP settings (required when enabled)
REQUEST_TIMEOUT 10s Timeout for NATS/monitoring calls
PAGINATION_DEFAULT_LIMIT 100 Default page size for list APIs
PAGINATION_MAX_LIMIT 500 Maximum allowed page size for list APIs
AUDIT_DEFAULT_LIMIT 50 Default page size for audit log when limit is omitted
LIVE_WS_MAX_MESSAGES 1000 Max messages per live WebSocket session
LIVE_WS_IDLE_TIMEOUT 5m Close idle live WebSocket connections after
LIVE_WS_RATE_LIMIT 100ms Minimum interval between live message frames
MAX_REQUEST_BODY_SIZE 1048576 Maximum API request body size in bytes (1 MiB)
HTTP_RESPONSE_COMPRESSION true Compress API/SPA responses (brotli/gzip) when the client accepts it
AUTH_RATE_LIMIT 10 Max auth attempts per IP per window
AUTH_RATE_LIMIT_WINDOW 1m Window for auth rate limiting
SLOW_CONSUMER_PENDING_THRESHOLD 1000 Pending msgs ≥ this → slow consumer
SLOW_CONSUMER_LAG_THRESHOLD 1000 Stream lag ≥ this → slow consumer
SLOW_CONSUMER_ACK_PENDING_RATIO 0.9 Ack-pending ≥ ratio × MaxAckPending → slow
BEHAVIOR_FINGERPRINT_KV_BUCKET nats_consol_fingerprints KV bucket for consumer behavior fingerprints
AI_ENABLED false Enable JetStream AI assistant (Gemini)
AI_API_KEY Google Gemini API key
AI_MODEL gemini-2.5-flash Gemini model name
AI_MAX_TOKENS 4096 Max response tokens
AI_REQUEST_TIMEOUT 60s LLM request timeout
AI_CONTEXT_CACHE_TTL 45s How long to cache JetStream context for the assistant
AI_GEMINI_API_BASE https://generativelanguage.googleapis.com/v1beta Gemini API base URL

JetStream AI Assistant

Built-in assistant scoped only to NATS JetStream and this console. Uses Google Gemini with your API key — billing via your Google account. Message payloads, credentials, and database data are never sent to the model.

AI_ENABLED=true
AI_API_KEY=your-gemini-api-key
AI_MODEL=gemini-2.5-flash

Open the AI floating button in the console (bottom-right) after signing in.

API: POST /api/v1/clusters/{clusterId}/assistant/chat

RBAC Roles

Role Permissions
root Single bootstrap superuser (is_root); full access; creates delegated admins with access rules
admin Full access when unscoped (legacy), or limited via accessRules when created by root
operator CRUD streams/consumers/KV/objects within assigned clusters
viewer Read-only (dashboard, browse, live tail) within assigned clusters

The bootstrap account (ADMIN_USERNAME / ADMIN_PASSWORD) is seeded as the root user on first start. Root can create additional admin users with configurable access rules. Operator and viewer users must be assigned at least one cluster via accessRules.clusterIds (multi-tenant scoping).

Access rule Meaning
clusterIds Required for non-root users (except legacy unscoped admin). Limits API access to listed cluster UUIDs. Empty list = no cluster access.
manageUsers Create, update, delete users and assign roles (delegated admin only)
viewAudit Read the audit log
assignableRoles Roles this admin may grant to others

Migration (v0.5): After upgrading, assign clusterIds to existing operator/viewer (and scoped admin) accounts that previously had implicit access to all clusters. Users with empty clusterIds lose cluster access until clusters are assigned.

Root cannot be deleted or demoted by non-root users. Only one root account may exist.

Security

NATS Consol applies defense-in-depth for browser and API traffic:

  • HTTP headersContent-Security-Policy, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, and Strict-Transport-Security when PUBLIC_BASE_URL uses HTTPS.
  • Cookies — Session cookies are HttpOnly, SameSite=Lax, and Secure in production or behind HTTPS. A separate CSRF cookie pairs with the X-CSRF-Token header for cookie-authenticated mutations.
  • CSRF — State-changing API requests authenticated via session cookie require a matching CSRF token. The SPA sends the token automatically.
  • CORS — Cross-origin access is denied unless the origin is listed in CORS_ALLOWED_ORIGINS (no wildcard reflection).
  • Rate limiting — Login endpoints are limited per client IP (AUTH_RATE_LIMIT, AUTH_RATE_LIMIT_WINDOW).
  • Request limits — Body size capped via MAX_REQUEST_BODY_SIZE; server read/write/idle timeouts via HTTP_*_TIMEOUT.
  • Payload compression — Bodies larger than 32 KiB are compressed (responses: brotli then gzip when negotiated via HTTP_RESPONSE_COMPRESSION; requests: brotli with gzip fallback). Smaller bodies stay uncompressed.
  • RBAC & audit — All /api/* routes except health/auth config require authentication. Mutations are audit-logged. Cluster tokens/creds are never returned in API JSON.
  • Production — Set ENV=production, ENCRYPTION_KEY, SESSION_PRIVATE_KEY / SESSION_PUBLIC_KEY, and a strong ADMIN_PASSWORD. The server refuses to start if these are missing or weak.

Run make test-security for automated checks (headers, cookies, CSRF, rate limits, RBAC, secret leakage).

API

See api/swagger.yaml (regenerate with make openapi) or live spec at GET /api/openapi.yaml.

Key endpoints:

  • GET /api/health — readiness (postgres + default NATS cluster)
  • GET /api/v1/clusters/{id}/metrics/history — JetStream/server metric history (Postgres snapshots)
  • GET /api/v1/auth/config{ auth_enabled, basic_enabled }
  • POST /api/v1/auth/login — session login
  • GET /api/v1/auth/me — current user profile
  • POST /api/v1/auth/logout — clear session cookie
  • GET /api/v1/audit — audit log (admin)
  • GET /api/v1/users — user list (admin)
  • GET /api/v1/clusters — list registered clusters (devops-configured)
  • Cluster-scoped JetStream, KV, Object Store, and live WebSocket paths under /api/v1/clusters/{id}/…

Helm

helm upgrade --install nats-consol ./deploy/helm/nats-consol \
  --set secrets.databaseUrl='postgres://…' \
  --set secrets.encryptionKey='your-32-char-key'

Testing

Tests are grouped by intent, not by framework. One shared tests/testutil package backs integration, contract, security, and database suites (testcontainers + in-memory HTTP).

Category Command Docker / stack required
Unit make test-unit No
Integration (API + NATS + DB) make test-integration Yes (testcontainers)
Database included in make test-integration Yes
Contract (camelCase JSON vs frontend) make test-contract Yes
Security (auth, RBAC, headers, CSRF, rate limits, no secrets in responses) make test-security Yes
Regression (CI gate) make test-regression Yes
Web unit + Playwright e2e make test-web No (mocked API e2e)
Smoke / E2E / Acceptance make test-smoke Yes (docker compose up)
Load / Throughput / Performance make test-performance Yes + vegeta
Stress make test-stress Yes + vegeta (higher rate)

Quick start:

make test-unit                              # fast, no Docker
make test-regression                        # integration + contract + security
docker compose up --build -d && make test-smoke
docker compose up -d && make test-performance   # needs vegeta installed
docker compose up -d && make test-stress        # needs vegeta; higher RPS

Set SKIP_TESTCONTAINERS=1 to skip Docker-backed Go tests.

Environment variables for smoke/performance/stress scripts:

Variable Default Description
BASE_URL http://localhost:8080 Running console URL
AUTH admin:admin Basic auth for smoke/perf/stress
TLS_INSECURE Set 1 to skip TLS verify when BASE_URL is HTTPS with a lab cert
PERF_MIN_RPS load: 10 / stress: 50 Minimum throughput
PERF_MAX_P99_MS load: 2000 / stress: 5000 Max p99 latency (ms)

CI (.github/workflows/test.yml) on every pull request to main: Go lint/tests/build, web lint/typecheck/build/Playwright e2e, regression (integration/contract/security), race detector (live WebSocket), and compose smoke (:8080). The All checks passed job must succeed before merge. Performance and stress baselines run on pushes to main only (advisory, not merge-blocking).

Post-deploy UI checks: see docs/manual-test-checklist.md.

Roadmap (v0.8+)

  • OpenAPI-generated CLI
  • Account/JWT operator key generation UI

License

Apache License 2.0 — see LICENSE.

About

Self-hosted NATS JetStream admin console with multi-cluster support.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages