Skip to content

ADR 25_nats_nkey

MaxThom edited this page Sep 5, 2025 · 1 revision

Architecture Design Record

ADR-25, Account Auth with nKeys

Extend CLI to have a server list

Checkout NSC and do some testing to manage a server https://docs.nats.io/using-nats/nats-tools/nsc

New Security Module

To use NKeys, we also need to make it great to use. We need to private the set of tools in the MirCLI to generate keys.

We need to be able to add new tenant (account) and add device (user) to the account.

Hierarchy:

  • Operators are responsible for running nats-servers, and issuing account JWTs. Operators set the limits on what an account can do, such as the number of connections, data limits, etc.
  • Accounts are responsible for issuing user JWTs. An account defines streams and services that can be exported to other accounts. Likewise, they import streams and services from other accounts.
  • Users are issued by an account, and encode limits regarding usage and authorization over the account's subject space.

Tutorial

CLI

o default to context name
u default to context url
a default to mir
--no-exec just print the commands

mir tools security init --operator <o> --url <u> --account <a>
  nsc add operator --generate-signing-key --sys --name <o>
  nsc edit operator --service-url <u> --account-jwt-server-url <u>
  nsc add account -n <a>

mir tools security edit --operator <o> --url <u>
  nsc env -o <o>
  nsc edit operator --service-url <u> --account-jwt-server-url <u>

path default to ./resolver.conf, must handle with or without filename
mir tools security generate-resolver --operator <o> <path>
  nsc env -o <o>
  nsc generate config --nats-resolver > <path>/resolver.conf

mir tools security env -o <o>
    nsc env -o <o>

mir tools security pull --operator <o>
  nsc env -o <o>
  nsc pull -A

mir tools security push --operator <o>
  nsc env -o <o>
  nsc push -A

mir tools security generate-creds --operator <o> --account <a> <user>
  nsc env -o <o>
  nsc generate creds -a <a> -n <user>

mir tools security add client --operator <o> --account <a> <name> --readonly
  nsc env -o <o>
  nsc add user -a <a> -n <name> \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "client.*.core.v1alpha.list" \
    --allow-pub "client.*.cmd.v1alpha.list" \
    --allow-pub "client.*.cfg.v1alpha.list" \
    --allow-pub "client.*.tlm.v1alpha.list" \
    --allow-pub "client.*.evt.v1alpha.list"

mir tools security add client --operator <o> --account <a> <name>
  nsc env -o <o>
  nsc add user -a <a> -n <name> \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "client.*.>"

mir tools security add client --operator <o> --account <a> <name> --swarm
  nsc env -o <o>
  nsc add user -a <a> -n <name> \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "client.*.>" \
    --allow-pub "device.*.>" \
    --allow-sub "*.>"

mir tools security add device --operator <o> --account <a> <name>
  nsc env -o <o>
  nsc add user -a <a> -n <name> \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "device.<name>.>" \
    --allow-sub "<name>.>"

mir tools security add device --operator <o> --account <a> <name> --wildcard
  nsc env -o <o>
  nsc add user -<a> -n <name> \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "device.*.>" \
    --allow-sub "*.>"

mir tools security add module --operator <o> --account <a> <name>
  nsc add user -a <a> -n <name> \
    --allow-pubsub "_INBOX.>" \
    --allow-pubsub "client.*.>" \
    --allow-pubsub "event.*.>" \
    --allow-sub "device.*.>" \
    --allow-pub "*.>"

Authentication

# Create operator with SYS user
nsc add operator --generate-signing-key --sys --name mir
# Set serving URL
nsc edit operator --service-url nats://localhost:4222 --account-jwt-server-url nats://localhost:4222

# See which operator and account in use and paths
nsc env

# Info
nsc describe operator mir
nsc describe account mir

# List keys
nsc list keys --all

# Generate resolver config
nsc generate config --nats-resolver > resolver.conf

# Start server with config below
nats-server -c config.conf

# Listen
nats sub --creds ~/.local/share/nats/nsc/keys/creds/mir/SYS/sys.creds ">"
nsc sub --user sys ">"

# Publish
nats pub --creds ~/.local/share/nats/nsc/keys/creds/mir/SYS/sys.creds hello NATS
nsc pub --user sys hello NATS

## Should not user SYS account, add new one
# Add account and user
nsc add account -n mir
nsc add user -a mir -n mir

# Update server with new auth
nsc push -a mir
# Pull config from server locally
nsc pull -A

# Test
nsc sub --user mir ">"
nsc pub --user mir hello NATS

config.conf

server_name: servertest
listen: 127.0.0.1:4222
http: 8222

jetstream: enabled

include resolver.conf

Authorization

Clients

# Read only
nsc [add|edit] user -a mir -n opread \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "client.*.core.v1alpha.list" \
    --allow-pub "client.*.cmd.v1alpha.list" \
    --allow-pub "client.*.cfg.v1alpha.list" \
    --allow-pub "client.*.tlm.v1alpha.list" \
    --allow-pub "client.*.evt.v1alpha.list"

# Read write
nsc [add|edit] user -a mir -n oprw \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "client.*.core.v1alpha.list" \
    --allow-pub "client.*.core.v1alpha.create" \
    --allow-pub "client.*.core.v1alpha.update" \
    --allow-pub "client.*.core.v1alpha.delete" \
    --allow-pub "client.*.cmd.v1alpha.list" \
    --allow-pub "client.*.cmd.v1alpha.send" \
    --allow-pub "client.*.cfg.v1alpha.list" \
    --allow-pub "client.*.cfg.v1alpha.send" \
    --allow-pub "client.*.tlm.v1alpha.list" \
    --allow-pub "client.*.evt.v1alpha.list" \
    --allow-pub "client.*.evt.v1alpha.delete"

# Read write simplified
nsc [add|edit] user -a mir -n oprw \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "client.*.>"

# Read write simplified with swarm capability
nsc [add|edit] user -a mir -n oprw \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "client.*.>" \
    --allow-pub "device.*.>" \
    --allow-sub "*.>"

Devices

# * can be replaced with device id
nsc [add|edit] user -a mir -n device \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "device.*.core.v1alpha.heartbeat" \
    --allow-pub "device.*.core.v1alpha.schema" \
    --allow-pub "device.*.tlm.v1alpha.proto" \
    --allow-pub "device.*.cfg.v1alpha.proto" \
    --allow-pub "device.*.cfg.v1alpha.desiredproperties" \
    --allow-sub "*.v1alpha.schema" \
    --allow-sub "*.v1alpha.config" \
    --allow-sub "*.v1alpha.command" \

# Simplified
# * can be replaced with device id
nsc [add|edit] user -a mir -n device \
    --allow-pubsub "_INBOX.>" \
    --allow-pub "device.*.>" \
    --allow-sub "*.>"

Modules

nsc [add|edit] user -a mir -n module \
    --allow-pubsub "_INBOX.>" \
    --allow-pubsub "client.*.>" \
    --allow-pubsub "event.*.>" \
    --allow-sub "device.*.>" \
    --allow-pub "*.>"

NATS Authentication Setup for Mir

Overview

This guide sets up NATS authentication using NKeys with two isolated accounts:

  • MirDev: Development environment
  • MirTest: Testing/staging environment

Generate Account Seeds

# Generate MirDev account
nk -gen account -pubout > mirdev.nk
# Example output:
# SAABXI3YM7XQL5J5VQZZYGGLLH3WPTGBUBP5QHHEV2GFHCF7LHCOZ4DEV
# ADDEV3JXYTXOJEL6LNAXDREUGRX35BOLZI3B4PFFAC7IRPR3OA4QNKDEV

# Generate MirTest account
nk -gen account -pubout > mirtest.nk
# Example output:
# SAACYX2KEOOQ52BMGX3W5F6L5OXQZJDVWT3GVWL3Y7HXSGVQRH2EATEST
# ACTEST7PZTKJSBTR7BF6TBK3D776734PWHWDKO7HFMQOM5BIOYPSTEST

Note: The first line is the seed (private key starting with 'S'), the second is the public key (starting with 'A').

NATS Server Configuration

Create nats-server.conf:

port: 4222

accounts {
  MirDev: {
    nkey: ADDEV3JXYTXOJEL6LNAXDREUGRX35BOLZI3B4PFFAC7IRPR3OA4QNKDEV
  }

  MirTest: {
    nkey: ACTEST7PZTKJSBTR7BF6TBK3D776734PWHWDKO7HFMQOM5BIOYPSTEST
  }
}

Store Seeds Securely

# Create directory
mkdir -p /etc/mir
chmod 700 /etc/mir

# Extract just the seeds (first line)
head -n1 mirdev.nk > /etc/mir/mirdev.seed
head -n1 mirtest.nk > /etc/mir/mirtest.seed

# Set restrictive permissions
chmod 600 /etc/mir/*.seed

# Remove original files with both keys
rm mirdev.nk mirtest.nk

Connect from Go

package main

import (
    "log"
    "github.com/nats-io/nats.go"
)

func main() {
    // Connect to MirDev
    ncDev, err := nats.Connect("nats://localhost:4222",
        nats.NkeyOptionFromSeed("/etc/mir/mirdev.seed"))
    if err != nil {
        log.Fatal("Dev connection failed:", err)
    }
    defer ncDev.Close()

    // Connect to MirTest
    ncTest, err := nats.Connect("nats://localhost:4222",
        nats.NkeyOptionFromSeed("/etc/mir/mirtest.seed"))
    if err != nil {
        log.Fatal("Test connection failed:", err)
    }
    defer ncTest.Close()
}

Test with CLI

# Start NATS server
nats-server -c nats-server.conf

# Test MirDev account (terminal 1)
nats sub --nkey=/etc/mir/mirdev.seed ">"

# Test MirTest account (terminal 2)
nats pub --nkey=/etc/mir/mirtest.seed test.subject "hello"

# Note: Messages published in MirTest won't be visible in MirDev subscription

Reload server.conf

1. Signal-based reload (most common):
 # Send HUP signal to NATS server process
 kill -HUP <nats-server-pid>

 # Or using systemctl if running as service
 systemctl reload nats-server

 2. Using NATS CLI:
 # Reload configuration
 nats-server --signal reload

 # Or using nats CLI tool
 nats server request reload

 3. Docker
 docker exec <nats-container> nats-server --signal reload
 ``

## Environment-Based Configuration

For easier switching between environments:

```bash
# Set environment variable
export NATS_NKEY_SEED_FILE=/etc/mir/mirdev.seed  # or mirtest.seed

# In your Go code
seedFile := os.Getenv("NATS_NKEY_SEED_FILE")
if seedFile == "" {
    seedFile = "/etc/mir/mirdev.seed" // default
}
nc, err := nats.Connect("nats://localhost:4222",
    nats.NkeyOptionFromSeed(seedFile))

Security Notes

  1. Never commit seed files to version control
  2. Keep different seeds for different environments
  3. Use file permissions to restrict access (600 or 400)
  4. Consider encrypting seeds at rest for production
  5. Backup seeds securely - losing them means losing access

Key Benefits

  • Complete isolation between Dev and Test environments
  • No passwords transmitted over network
  • Simple management - just one seed per environment
  • Secure - uses Ed25519 public key cryptography
  • Easy rotation - generate new keys anytime

Clone this wiki locally