From 178e4b34c7ca6d4c7a8fd94d2717e6b2cec5bb42 Mon Sep 17 00:00:00 2001 From: "Scott \"R00tPiG Security\" Lindsey (r00tpig)" Date: Sat, 12 Sep 2026 00:00:06 -0400 Subject: [PATCH 1/4] Test env NATS cluster cut-over --- NATS.md | 3 +++ README.md | 8 ++++++++ nats.conf | 25 +++++++++++++++++++++++++ package.json | 10 ++++++++++ policy.json | 35 +++++++++++++++++++++++++++++++++++ src/policy.js | 14 ++++++++++++++ test/policy.test.js | 12 ++++++++++++ 7 files changed, 107 insertions(+) create mode 100644 NATS.md create mode 100644 README.md create mode 100644 nats.conf create mode 100644 package.json create mode 100644 policy.json create mode 100644 src/policy.js create mode 100644 test/policy.test.js diff --git a/NATS.md b/NATS.md new file mode 100644 index 0000000..c782336 --- /dev/null +++ b/NATS.md @@ -0,0 +1,3 @@ +# NATS — verae-nats-accounts + +Not a subscriber. Defines INTERNAL vs LEAF. Leaf deny `verae.billing.>`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1284304 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# verae-nats-accounts + +NATS **account + leaf allow-list**. Leaf nodes cannot publish `verae.billing.*`. + +**Forgejo:** https://git.georgelambert.org/marchon/verae-nats-accounts + +- `policy.json` — machine-readable allow/deny (used by access-leaf). +- `nats.conf` — sketch for a future accounts-enabled nats-server (loopback). diff --git a/nats.conf b/nats.conf new file mode 100644 index 0000000..857af4b --- /dev/null +++ b/nats.conf @@ -0,0 +1,25 @@ +# Lab accounts sketch. Loopback only. Do not publish 4222. +# INTERNAL: middleware, account-balance, authz, identity. +# LEAF: access-leaf and remote leaf nodes — no billing. + +# Loopback sketch. The live 3-node test cluster is verae-nats-cluster +# (10.10.10.21–23 on vmbr1) and currently runs without accounts so every +# existing NATS_URL keeps working. Flip this file onto those guests only +# after clients use nats://internal:internal-dev@host:4222. +listen: 127.0.0.1:4222 +http: 127.0.0.1:8222 +jetstream {} + +accounts { + INTERNAL { + users = [ { user: internal, password: internal-dev } ] + jetstream: enabled + } + LEAF { + users = [ { user: leaf, password: leaf-dev } ] + exports = [] + imports = [] + } +} +# Subject restrictions for LEAF are enforced in policy.json + access-leaf +# until this nats-server is started with full auth callout. diff --git a/package.json b/package.json new file mode 100644 index 0000000..0213cab --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "verae-nats-accounts", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "NATS account and leaf subject policy (INTERNAL vs LEAF)", + "scripts": { + "test": "node --test test/*.test.js" + } +} diff --git a/policy.json b/policy.json new file mode 100644 index 0000000..8c5a59c --- /dev/null +++ b/policy.json @@ -0,0 +1,35 @@ +{ + "accounts": { + "INTERNAL": { + "publish": [">"], + "subscribe": [">"] + }, + "LEAF": { + "publish": [ + "verae.access.leaf.in", + "verae.archive.put", + "verae.archive.query", + "verae.archive.reply.*", + "verae.zapier.jobs.watch", + "verae.zapier.jobs.events", + "verae.zapier.webhooks.deliver" + ], + "subscribe": [ + "verae.access.leaf.in", + "verae.archive.>", + "verae.zapier.jobs.>", + "verae.zapier.webhooks.deliver" + ], + "deny_publish": ["verae.billing.>", "verae.access.authz.>"], + "deny_subscribe": ["verae.billing.>"] + } + }, + "leafAllow": [ + "verae.archive.put", + "verae.archive.query", + "verae.archive.reply.", + "verae.zapier.jobs.watch", + "verae.zapier.jobs.events", + "verae.zapier.webhooks.deliver" + ] +} diff --git a/src/policy.js b/src/policy.js new file mode 100644 index 0000000..35bb5af --- /dev/null +++ b/src/policy.js @@ -0,0 +1,14 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const FILE = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'policy.json'); + +export function loadPolicy() { + return JSON.parse(fs.readFileSync(FILE, 'utf8')); +} + +export function leafAllowed(subject) { + const allow = loadPolicy().leafAllow || []; + return allow.some((p) => (p.endsWith('.') ? subject.startsWith(p) : subject === p || subject.startsWith(`${p}.`))); +} diff --git a/test/policy.test.js b/test/policy.test.js new file mode 100644 index 0000000..0cc9ca0 --- /dev/null +++ b/test/policy.test.js @@ -0,0 +1,12 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { leafAllowed, loadPolicy } from '../src/policy.js'; + +test('leaf may archive and jobs, never billing', () => { + assert.equal(leafAllowed('verae.archive.put'), true); + assert.equal(leafAllowed('verae.archive.reply.abc'), true); + assert.equal(leafAllowed('verae.zapier.jobs.watch'), true); + assert.equal(leafAllowed('verae.billing.balance.adjust'), false); + assert.equal(leafAllowed('verae.billing.statement.get'), false); + assert.ok(loadPolicy().accounts.LEAF.deny_publish.includes('verae.billing.>')); +}); From f813d4003d30994945abac77839ab0f56945417e Mon Sep 17 00:00:00 2001 From: George Lambert Date: Sat, 12 Sep 2026 00:00:06 -0400 Subject: [PATCH 2/4] Test env NATS cluster cut-over --- NATS.md | 3 +++ README.md | 8 ++++++++ nats.conf | 25 +++++++++++++++++++++++++ package.json | 10 ++++++++++ policy.json | 35 +++++++++++++++++++++++++++++++++++ src/policy.js | 14 ++++++++++++++ test/policy.test.js | 12 ++++++++++++ 7 files changed, 107 insertions(+) create mode 100644 NATS.md create mode 100644 README.md create mode 100644 nats.conf create mode 100644 package.json create mode 100644 policy.json create mode 100644 src/policy.js create mode 100644 test/policy.test.js diff --git a/NATS.md b/NATS.md new file mode 100644 index 0000000..c782336 --- /dev/null +++ b/NATS.md @@ -0,0 +1,3 @@ +# NATS — verae-nats-accounts + +Not a subscriber. Defines INTERNAL vs LEAF. Leaf deny `verae.billing.>`. diff --git a/README.md b/README.md new file mode 100644 index 0000000..1284304 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# verae-nats-accounts + +NATS **account + leaf allow-list**. Leaf nodes cannot publish `verae.billing.*`. + +**Forgejo:** https://git.georgelambert.org/marchon/verae-nats-accounts + +- `policy.json` — machine-readable allow/deny (used by access-leaf). +- `nats.conf` — sketch for a future accounts-enabled nats-server (loopback). diff --git a/nats.conf b/nats.conf new file mode 100644 index 0000000..857af4b --- /dev/null +++ b/nats.conf @@ -0,0 +1,25 @@ +# Lab accounts sketch. Loopback only. Do not publish 4222. +# INTERNAL: middleware, account-balance, authz, identity. +# LEAF: access-leaf and remote leaf nodes — no billing. + +# Loopback sketch. The live 3-node test cluster is verae-nats-cluster +# (10.10.10.21–23 on vmbr1) and currently runs without accounts so every +# existing NATS_URL keeps working. Flip this file onto those guests only +# after clients use nats://internal:internal-dev@host:4222. +listen: 127.0.0.1:4222 +http: 127.0.0.1:8222 +jetstream {} + +accounts { + INTERNAL { + users = [ { user: internal, password: internal-dev } ] + jetstream: enabled + } + LEAF { + users = [ { user: leaf, password: leaf-dev } ] + exports = [] + imports = [] + } +} +# Subject restrictions for LEAF are enforced in policy.json + access-leaf +# until this nats-server is started with full auth callout. diff --git a/package.json b/package.json new file mode 100644 index 0000000..0213cab --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "verae-nats-accounts", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "NATS account and leaf subject policy (INTERNAL vs LEAF)", + "scripts": { + "test": "node --test test/*.test.js" + } +} diff --git a/policy.json b/policy.json new file mode 100644 index 0000000..8c5a59c --- /dev/null +++ b/policy.json @@ -0,0 +1,35 @@ +{ + "accounts": { + "INTERNAL": { + "publish": [">"], + "subscribe": [">"] + }, + "LEAF": { + "publish": [ + "verae.access.leaf.in", + "verae.archive.put", + "verae.archive.query", + "verae.archive.reply.*", + "verae.zapier.jobs.watch", + "verae.zapier.jobs.events", + "verae.zapier.webhooks.deliver" + ], + "subscribe": [ + "verae.access.leaf.in", + "verae.archive.>", + "verae.zapier.jobs.>", + "verae.zapier.webhooks.deliver" + ], + "deny_publish": ["verae.billing.>", "verae.access.authz.>"], + "deny_subscribe": ["verae.billing.>"] + } + }, + "leafAllow": [ + "verae.archive.put", + "verae.archive.query", + "verae.archive.reply.", + "verae.zapier.jobs.watch", + "verae.zapier.jobs.events", + "verae.zapier.webhooks.deliver" + ] +} diff --git a/src/policy.js b/src/policy.js new file mode 100644 index 0000000..35bb5af --- /dev/null +++ b/src/policy.js @@ -0,0 +1,14 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const FILE = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'policy.json'); + +export function loadPolicy() { + return JSON.parse(fs.readFileSync(FILE, 'utf8')); +} + +export function leafAllowed(subject) { + const allow = loadPolicy().leafAllow || []; + return allow.some((p) => (p.endsWith('.') ? subject.startsWith(p) : subject === p || subject.startsWith(`${p}.`))); +} diff --git a/test/policy.test.js b/test/policy.test.js new file mode 100644 index 0000000..0cc9ca0 --- /dev/null +++ b/test/policy.test.js @@ -0,0 +1,12 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { leafAllowed, loadPolicy } from '../src/policy.js'; + +test('leaf may archive and jobs, never billing', () => { + assert.equal(leafAllowed('verae.archive.put'), true); + assert.equal(leafAllowed('verae.archive.reply.abc'), true); + assert.equal(leafAllowed('verae.zapier.jobs.watch'), true); + assert.equal(leafAllowed('verae.billing.balance.adjust'), false); + assert.equal(leafAllowed('verae.billing.statement.get'), false); + assert.ok(loadPolicy().accounts.LEAF.deny_publish.includes('verae.billing.>')); +}); From 67a2627787ead64a72d882449fbb1eb4b556d227 Mon Sep 17 00:00:00 2001 From: "Scott \"R00tPiG Security\" Lindsey (r00tpig)" Date: Tue, 22 Sep 2026 09:58:31 -0400 Subject: [PATCH 3/4] docs: bring README.md in line with Standard Readme --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 1284304..c318dbb 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,32 @@ # verae-nats-accounts +[![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) + NATS **account + leaf allow-list**. Leaf nodes cannot publish `verae.billing.*`. **Forgejo:** https://git.georgelambert.org/marchon/verae-nats-accounts - `policy.json` — machine-readable allow/deny (used by access-leaf). - `nats.conf` — sketch for a future accounts-enabled nats-server (loopback). + +## Install + +```sh +git clone ssh://git@git.georgelambert.org:2223/marchon/verae-nats-accounts.git +cd verae-nats-accounts +``` + +## Usage + +```sh +# No single entrypoint is declared in this tree. +ls +``` + +## Contributing + +Questions: open an issue at https://git.georgelambert.org/marchon/verae-nats-accounts/issues. Pull requests are accepted. + +## License + +UNLICENSED © George Lambert From 07911946650dba83597957a797c436b60ab3284f Mon Sep 17 00:00:00 2001 From: "Scott \"R00tPiG Security\" Lindsey (r00tpig)" Date: Mon, 12 Oct 2026 20:02:43 -0400 Subject: [PATCH 4/4] docs: set OWNER/AUTHORS credit to rootpig (Scott R00tPiG Lindsey) --- AUTHORS | 1 + OWNER.md | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 AUTHORS create mode 100644 OWNER.md diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..846a4f5 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Scott "R00tPiG Security" Lindsey (rootpig) — owner diff --git a/OWNER.md b/OWNER.md new file mode 100644 index 0000000..e7f075e --- /dev/null +++ b/OWNER.md @@ -0,0 +1,10 @@ +# Repository owner + +**Owner / credited author:** Scott "R00tPiG Security" Lindsey (`rootpig`) + +Forgejo: https://git.georgelambert.org/rootpig + +This repository is owned by **rootpig** for Verae Inc. server maintenance, +deployment scripting, and related operations work. + +Co-contributors may appear in git history; ownership and ops credit belong to rootpig.