nats-auth-operator

command module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 20, 2026 License: Apache-2.0 Imports: 12 Imported by: 0

README ΒΆ

NATS Authentication Operator

License Go Report Card

A Kubernetes operator for managing NATS authentication using JWT and token-based authentication. This operator provides a modern, declarative way to manage NATS users and accounts as Kubernetes custom resources.

Note: This operator manages only authentication - it does NOT manage NATS clusters, streams, or consumers. Use the NATS Helm chart for cluster management and NACK for JetStream resource management.

Features

Authentication Modes
  • JWT-based authentication - Full NATS Operator β†’ Accounts β†’ Users hierarchy with automatic JWT generation
  • Token-based authentication - Simple username/password credentials
  • JetStream support - Built-in JetStream limits configuration for accounts
Key Capabilities
  • πŸ” Automatic credential generation - Operator keypairs, account JWTs, user JWTs, and .creds files
  • 🎯 Fine-grained permissions - Publish/subscribe controls with allow/deny lists
  • πŸ”„ Seamless NATS Helm integration - Works alongside official NATS Helm chart without conflicts
  • πŸ“¦ Kubernetes-native - Manage authentication using kubectl and GitOps workflows
  • πŸ›‘οΈ Production-ready - Idempotent reconciliation, finalizers, status conditions, secure secret storage
  • ⚑ Automatic change propagation - Spec changes (permissions, limits) are detected and re-applied on the next reconcile
  • πŸ” Force-sync annotation - Trigger an immediate reconcile on any resource without waiting

Quick Start

Installation
helm install nats-auth-operator oci://ghcr.io/jradikk/charts/nats-auth-operator \
  --version 0.1.0 \
  --namespace nats-system \
  --create-namespace
Using kubectl
kubectl apply -f https://raw.githubusercontent.com/jradikk/nats-auth-operator/main/dist/install.yaml
Basic Usage
  1. Create an auth configuration:
apiVersion: nats.jradikk/v1alpha1
kind: NatsAuthConfig
metadata:
  name: main
  namespace: default
spec:
  natsURL: "nats://nats.default.svc.cluster.local:4222"
  mode: jwt
  serverAuthConfig:
    name: "nats-auth-jwts"
    namespace: "default"
    type: "Secret"
  jwt:
    operatorName: "MyOperator"
  1. Create accounts:
apiVersion: nats.jradikk/v1alpha1
kind: NatsAccount
metadata:
  name: myapp-account
  namespace: default
spec:
  authConfigRef:
    name: main
  description: "Application account with JetStream"
  limits:
    conn: 100
    subs: 1000
    jetstream:
      memoryStorage: -1  # Unlimited
      diskStorage: -1
      streams: -1
      consumer: -1
  1. Create users:
apiVersion: nats.jradikk/v1alpha1
kind: NatsUser
metadata:
  name: app-user
  namespace: default
spec:
  authConfigRef:
    name: main
  authType: jwt
  accountRef:
    name: myapp-account
  username: "app-user"
  permissions:
    publishAllow:
      - "app.>"
      - "$JS.ACK.>"
      - "_INBOX.>"
    subscribeAllow:
      - "app.>"
      - "$JS.API.>"
      - "_INBOX.>"
  1. Use the generated credentials:

The operator creates a Secret app-user-user-creds containing:

  • user.creds - NATS credentials file (JWT + seed)
  • user.jwt - User JWT
  • NATS_URL - NATS server URL

Mount this secret in your pod:

apiVersion: v1
kind: Pod
metadata:
  name: myapp
spec:
  containers:
  - name: app
    image: myapp:latest
    volumeMounts:
    - name: nats-creds
      mountPath: /var/run/nats
      readOnly: true
    env:
    - name: NATS_CREDS_FILE
      value: /var/run/nats/user.creds
  volumes:
  - name: nats-creds
    secret:
      secretName: app-user-user-creds

Architecture

Custom Resource Definitions (CRDs)
  1. NatsAuthConfig - Global authentication configuration

    • Defines authentication mode (JWT/token)
    • Specifies NATS server URL
    • References where to store generated credentials
  2. NatsAccount - NATS account (JWT mode only)

    • Defines account limits (connections, subscriptions, payload size)
    • Configures JetStream limits (storage, streams, consumers)
    • Generates account JWT signed by operator
  3. NatsUser - NATS user

    • Defines user permissions (publish/subscribe allow/deny lists)
    • Generates user JWT or username/password credentials
    • Creates Kubernetes Secret with credentials
How It Works
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  NatsAuthConfig     β”‚ Creates operator keypair
β”‚  (JWT mode)         β”‚ Stores in Secret
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚                   β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”
   β”‚  NatsAccount   β”‚  β”‚ NatsAccount  β”‚ Generate account
   β”‚  (system)      β”‚  β”‚ (myapp)      β”‚ JWTs, store in
   β””β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜ individual Secrets
           β”‚                   β”‚
           β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
           β”‚         β”‚                    β”‚
       β”Œβ”€β”€β”€β–Όβ”€β”€β”€β”€β” β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”
       β”‚  User  β”‚ β”‚  User  β”‚  ...  β”‚  User    β”‚ Generate user
       β”‚  (u1)  β”‚ β”‚  (u2)  β”‚       β”‚  (un)    β”‚ JWTs + creds
       β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ files
           β”‚         β”‚                   β”‚
           β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                     β”‚
          β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
          β”‚  nats-auth-jwts      β”‚ Aggregated Secret
          β”‚  Secret              β”‚ referenced by NATS
          β”‚  - operator JWT      β”‚ Helm chart
          β”‚  - system-account    β”‚
          β”‚  - myapp-account     β”‚
          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Integration with NATS Helm Chart

The operator is designed to work seamlessly with the official NATS Helm chart.

Deploy NATS

After the operator has reconciled and nats-auth-jwts Secret exists:

helm repo add nats https://nats-io.github.io/k8s/helm/charts/
helm upgrade --install nats nats/nats -f examples/nats-values.yaml

See examples/nats-values.yaml for a complete working Helm values file.

Key fields to configure in your values:

config:
  merge:
    operator: << $NATS_OPERATOR_JWT >>
    system_account: "<main-system-account public key>"
    resolver_preload:
      "<main-system-account public key>": << $NATS_SYSTEM_ACCOUNT_JWT >>

container:
  env:
    NATS_OPERATOR_JWT:
      valueFrom:
        secretKeyRef:
          name: nats-auth-jwts
          key: operator
    NATS_SYSTEM_ACCOUNT_JWT:
      valueFrom:
        secretKeyRef:
          name: nats-auth-jwts
          key: main-system-account  # key = <authconfig-name>-system-account

Get the system account public key after the operator reconciles:

kubectl get natsaccount main-system-account -o jsonpath='{.status.accountId}'

No conflicts: the operator manages credentials (Secrets), the NATS Helm chart manages the server (ConfigMaps, StatefulSet). Each owns distinct resources.

Examples

See the examples/ directory for complete examples:

JetStream Permissions

When using JetStream, users need specific permissions:

For JetStream Consumers:
publishAllow:
  - "$JS.ACK.<stream-name>.>"    # Acknowledge messages
  - "$JS.API.>"                  # JetStream API calls
  - "_INBOX.>"                   # Request-response

subscribeAllow:
  - "your.subjects.>"            # Stream subjects
  - "$JS.API.>"                  # JetStream API
  - "_INBOX.>"                   # Message delivery
For Stream-Specific Access:
publishAllow:
  - "$JS.ACK.mystream.>"
  - "$JS.API.CONSUMER.*.mystream.>"

subscribeAllow:
  - "$JS.API.CONSUMER.*.mystream.>"
  - "$JS.API.STREAM.INFO.mystream"
  - "_INBOX.>"

Force-Sync

Annotate any resource with nats.jradikk/force-sync to trigger an immediate full reconcile, bypassing the idempotency guards. The annotation is removed automatically after processing.

# Force re-generate a user JWT (e.g., after manually editing permissions)
kubectl annotate NatsUser my-user nats.jradikk/force-sync=$(date +%s) --overwrite

# Force re-generate an account JWT (e.g., after changing limits)
kubectl annotate NatsAccount my-account nats.jradikk/force-sync=$(date +%s) --overwrite

# Force NatsAuthConfig to re-collect and re-publish all account JWTs
kubectl annotate NatsAuthConfig my-config nats.jradikk/force-sync=$(date +%s) --overwrite

Under normal operation this is not needed β€” spec changes are detected automatically via the resource generation. Use force-sync when you have edited secrets manually or need to verify the operator is in sync.

Troubleshooting

Account IDs Keep Changing

Problem: Account IDs rotate constantly, causing authentication failures.

Cause: Infinite reconciliation loop due to account status/secret mismatch.

Solution: The operator now verifies that the account ID in status matches the seed in the secret. Rebuild and redeploy the operator.

"JetStream not enabled for account" Error

Problem: JetStream operations fail with error code 10039.

Solution: Add JetStream limits to your NatsAccount:

limits:
  jetstream:
    memoryStorage: -1
    diskStorage: -1
    streams: -1
    consumer: -1

Even with all values set to -1 (unlimited), the presence of the jetstream section enables JetStream for the account.

"Authorization Violation" with JetStream

Problem: Client gets "Authorization Violation" when using JetStream consumers.

Possible Causes:

  1. Missing publish permissions for acknowledgments:

    publishAllow:
      - "$JS.ACK.>"      # Required!
      - "_INBOX.>"       # Required!
    
  2. Missing subscribe permissions for JetStream API:

    subscribeAllow:
      - "$JS.API.>"      # Required!
      - "_INBOX.>"       # Required!
    
  3. Credentials not mounted correctly:

    • Ensure secret is mounted as a file
    • Application must use user.creds file, not user.jwt
    • Check NATS server logs for "authentication error"
Secret Missing Account JWT

Problem: The main Secret (e.g., nats-auth-jwts) is missing an account JWT.

Solution:

  1. Check if NatsAccount is Ready:

    kubectl get natsaccounts
    
  2. Check if individual account Secret exists:

    kubectl get secret <account-name>-account-jwt
    
  3. Trigger reconciliation with the force-sync annotation:

    kubectl annotate natsauthconfig main nats.jradikk/force-sync=$(date +%s) --overwrite
    

    The operator will re-collect all account JWTs and rewrite the Secret.

Account ID Mismatch Between JWT and Status

Problem: The account public key in the JWT doesn't match the status.

Solution: Delete the account JWT secret to force regeneration:

kubectl delete secret <account-name>-account-jwt

The operator will regenerate the JWT using the existing seed.

User Credentials Keep Regenerating

Problem: User credentials change on every reconciliation.

Cause: status.observedGeneration is out of sync with the actual resource generation, causing the controller to treat every reconcile as a spec change.

Solution: Check whether the status is being updated correctly. If the problem persists, delete and recreate the user resource to reset the generation counter.

NATS Server Shows "authentication error"

Problem: NATS logs show authentication errors despite correct JWT.

Possible Causes:

  1. JWT signed by wrong account - Check issuer in JWT matches account ID
  2. Stale credentials - Application using old credentials file
  3. Account not in operator's resolver - Check main Secret has the account JWT

Debug:

# Check what's in the main Secret
kubectl get secret nats-auth-jwts -o jsonpath='{.data}' | jq 'keys'

# Decode and inspect a JWT
kubectl get secret <user>-user-creds -o jsonpath='{.data.user\.jwt}' | base64 -d

Development

Prerequisites
  • Go 1.24+
  • Kubernetes cluster (kind, minikube, or real cluster)
  • kubectl
  • Docker or Podman
Building
# Build the operator
make build

# Build Docker image
make docker-build IMG=myregistry/nats-auth-operator:latest

# Push Docker image
make docker-push IMG=myregistry/nats-auth-operator:latest
Running Locally
# Install CRDs
make install

# Run operator locally (against your current kubectl context)
make run
Testing
# Run tests
make test

# Run with coverage
make test-coverage

Contributing

Contributions are welcome! Please:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. Submit a pull request

License

Apache 2.0 License. See LICENSE for details.

Acknowledgments

Documentation ΒΆ

The Go Gopher

There is no documentation for this package.

Directories ΒΆ

Path Synopsis
api
v1alpha1
Package v1alpha1 contains API Schema definitions for the nats v1alpha1 API group +kubebuilder:object:generate=true +groupName=nats.jradikk
Package v1alpha1 contains API Schema definitions for the nats v1alpha1 API group +kubebuilder:object:generate=true +groupName=nats.jradikk
internal
jwt

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL