Close remaining IAM JSON doors and re-walk the lab.
Some checks are pending
offline / test (push) Waiting to run
Some checks are pending
offline / test (push) Waiting to run
CS/sales/accounting/staff list endpoints and sales pricing writes require IAM; fleet service env sets STAFF_AUTH=1. Department tests use a private books file so lab NATS does not leak into them.
This commit is contained in:
parent
c32b65038a
commit
2bb3884a1b
14 changed files with 37 additions and 8 deletions
|
|
@ -51,6 +51,7 @@ const server = http.createServer(async (req, res) => {
|
|||
return json(200, { ok: true, role: 'verae-access-staff', plane: PLANE });
|
||||
}
|
||||
if (req.method === 'GET' && url.pathname === '/customers') {
|
||||
if (!(await denyOrRedirect(req, res, json, { permission: 'staff.plane' }))) return;
|
||||
return json(200, { customers: await listCustomers(EDGE, KEY) });
|
||||
}
|
||||
const review = url.pathname.match(/^\/review\/([^/]+)$/);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
|
|
@ -12,14 +14,15 @@ test('web plane can read statement after authz, cannot skip authz', async () =>
|
|||
const authzPort = 18021;
|
||||
const booksPort = 18022;
|
||||
const webPort = 18023;
|
||||
const booksFile = path.join(os.tmpdir(), `web-books-${process.pid}.json`);
|
||||
const authz = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: authzRoot,
|
||||
env: { ...process.env, PORT: String(authzPort) },
|
||||
env: { ...process.env, PORT: String(authzPort), NATS_URL: '' },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const books = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: booksRoot,
|
||||
env: { ...process.env, PORT: String(booksPort) },
|
||||
env: { ...process.env, PORT: String(booksPort), BOOKS_PATH: booksFile, NATS_URL: '' },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const web = spawn(process.execPath, ['src/server.js'], {
|
||||
|
|
@ -29,6 +32,7 @@ test('web plane can read statement after authz, cannot skip authz', async () =>
|
|||
PORT: String(webPort),
|
||||
AUTHZ_URL: `http://127.0.0.1:${authzPort}`,
|
||||
ACCOUNT_BALANCE_URL: `http://127.0.0.1:${booksPort}`,
|
||||
NATS_URL: '',
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
|
@ -51,5 +55,6 @@ test('web plane can read statement after authz, cannot skip authz', async () =>
|
|||
web.kill('SIGTERM');
|
||||
books.kill('SIGTERM');
|
||||
authz.kill('SIGTERM');
|
||||
fs.rmSync(booksFile, { force: true });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@
|
|||
"PORT": "3025",
|
||||
"AUTHZ_URL": "http://127.0.0.1:3020",
|
||||
"ACCOUNT_BALANCE_URL": "http://127.0.0.1:3010",
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028"
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028",
|
||||
"STAFF_AUTH": "1"
|
||||
},
|
||||
"nats": { "in": [], "out": ["verae.access.authz.check"] }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@
|
|||
"ZAPPIER_ADMIN_URL": "http://127.0.0.1:3000",
|
||||
"ACCOUNT_BALANCE_URL": "http://127.0.0.1:3010",
|
||||
"NATS_URL": "nats://127.0.0.1:4222",
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028"
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028",
|
||||
"STAFF_AUTH": "1"
|
||||
},
|
||||
"nats": { "in": [], "out": ["verae.billing.statement.get"] }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
"ACCOUNT_BALANCE_URL": "http://127.0.0.1:3010",
|
||||
"NATS_URL": "nats://127.0.0.1:4222",
|
||||
"AUTHZ_URL": "http://127.0.0.1:3020",
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028"
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028",
|
||||
"STAFF_AUTH": "1"
|
||||
},
|
||||
"nats": {
|
||||
"in": [],
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@
|
|||
"ACCOUNT_BALANCE_URL": "http://127.0.0.1:3010",
|
||||
"NATS_URL": "nats://127.0.0.1:4222",
|
||||
"AUTHZ_URL": "http://127.0.0.1:3020",
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028"
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028",
|
||||
"STAFF_AUTH": "1"
|
||||
},
|
||||
"nats": { "in": [], "out": ["verae.billing.statement.get"] }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"env": {
|
||||
"PORT": "3000",
|
||||
"STAFF_IAM_URL": "http://127.0.0.1:3028",
|
||||
"STAFF_AUTH": "1",
|
||||
"BIND": "127.0.0.1",
|
||||
"ZAPPIER_UPSTREAM": "http://127.0.0.1:3100",
|
||||
"CS_SERVICE_URL": "http://127.0.0.1:3011",
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ Port **`:3028`**. UI: sign-in, people, roles, audit.
|
|||
| `accounting` | `acct-dev-key` | accounting |
|
||||
| `operator` | `fleet-dev-key` | operator |
|
||||
|
||||
Override with `IAM_OWNER_PASSWORD`, `IAM_CS_PASSWORD`, etc. Persist: `STAFF_IAM_PATH` (users, audit, **sessions**). Login is rate-limited (8 failures / 10 minutes / IP).
|
||||
Override with `IAM_OWNER_PASSWORD`, `IAM_CS_PASSWORD`, etc. Persist: `STAFF_IAM_PATH` (users, audit, **sessions**). Login is rate-limited (8 failures / 10 minutes / IP+username). JSON APIs return 401/403; HTML doors 302 to `/login`.
|
||||
|
||||
## Wire other doors
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,12 @@
|
|||
- Disable lan-134 unless `FLEET_ENABLE_LAN134=1`.
|
||||
- SSH spawn timeout 8s; failed hosts skipped.
|
||||
|
||||
## 2026-09-11 — IAM JSON doors closed
|
||||
|
||||
- CS/sales/accounting/staff JSON list and sales PUT pricing now go through IAM (401 JSON, 302 HTML).
|
||||
- Fleet service env sets `STAFF_AUTH=1` with `STAFF_IAM_URL`.
|
||||
- Re-walk: cs credit 200 agent=`cs`; cs export 403; operator Fleet POST 200; admin `/me` `permissions:['*']`.
|
||||
|
||||
## 2026-09-11 — IAM on lab, portal door, hardening
|
||||
|
||||
- Restarted fleet with `STAFF_IAM_URL`. Walk: cs credits (agent=`cs`) and is 403 on accounting export; operator fleet POST 200, cs 403; admin `/me` permissions `*`.
|
||||
|
|
|
|||
|
|
@ -7,3 +7,5 @@
|
|||
- [x] Staff IAM: named users, roles, permissions (`verae-staff-iam` :3028).
|
||||
- [ ] Zapier Platform `push` of a private app.
|
||||
- [x] Move portal static files fully into `verae-access-web` (`/portal/` public door; API proxied to loopback edge).
|
||||
- [x] Turn IAM on for the running lab (fleet `STAFF_IAM_URL` + restart).
|
||||
- [x] IAM hardening: JSON sessions, login rate-limit, JSON 401 as well as HTML 302, credit principal = IAM username.
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ const server = http.createServer(async (req, res) => {
|
|||
return json(200, { ok: true, role: 'zappier-accounting-export' });
|
||||
}
|
||||
if (req.method === 'GET' && url.pathname === '/customers') {
|
||||
if (!(await denyOrRedirect(req, res, json, { permission: 'accounting.review' }))) return;
|
||||
return json(200, { customers: await listCustomers(EDGE, KEY) });
|
||||
}
|
||||
const review = url.pathname.match(/^\/review\/([^/]+)$/);
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@ const server = http.createServer(async (req, res) => {
|
|||
return json(r.status, body);
|
||||
}
|
||||
if (req.method === 'GET' && url.pathname === '/credits') {
|
||||
if (!(await denyOrRedirect(req, res, json, { permission: 'cs.review' }))) return;
|
||||
const id = url.searchParams.get('customerId');
|
||||
if (!id) return json(400, { error: 'customerId required' });
|
||||
const out = await statement(id);
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { test } from 'node:test';
|
|||
import assert from 'node:assert/strict';
|
||||
import { spawn } from 'node:child_process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
const root = path.dirname(fileURLToPath(new URL('.', import.meta.url)));
|
||||
|
|
@ -29,9 +31,10 @@ test('customer-service health', async () => {
|
|||
test('customer-service review via account-balance HTTP', async () => {
|
||||
const booksPort = 18014;
|
||||
const csPort = 18015;
|
||||
const booksFile = path.join(os.tmpdir(), `cs-books-${process.pid}.json`);
|
||||
const books = spawn(process.execPath, ['src/server.js'], {
|
||||
cwd: path.join(root, '..', 'zappier-account-balance'),
|
||||
env: { ...process.env, PORT: String(booksPort) },
|
||||
env: { ...process.env, PORT: String(booksPort), BOOKS_PATH: booksFile, NATS_URL: '' },
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
const cs = spawn(process.execPath, ['src/server.js'], {
|
||||
|
|
@ -40,6 +43,8 @@ test('customer-service review via account-balance HTTP', async () => {
|
|||
...process.env,
|
||||
PORT: String(csPort),
|
||||
ACCOUNT_BALANCE_URL: `http://127.0.0.1:${booksPort}`,
|
||||
NATS_URL: '',
|
||||
STAFF_IAM_URL: '',
|
||||
},
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
});
|
||||
|
|
@ -58,5 +63,6 @@ test('customer-service review via account-balance HTTP', async () => {
|
|||
} finally {
|
||||
cs.kill('SIGTERM');
|
||||
books.kill('SIGTERM');
|
||||
fs.rmSync(booksFile, { force: true });
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ const server = http.createServer(async (req, res) => {
|
|||
return json(200, { ok: true, role: 'zappier-sales-pricing' });
|
||||
}
|
||||
if (req.method === 'GET' && url.pathname === '/customers') {
|
||||
if (!(await denyOrRedirect(req, res, json, { permission: 'sales.review' }))) return;
|
||||
return json(200, { customers: await listCustomers(EDGE, KEY) });
|
||||
}
|
||||
const review = url.pathname.match(/^\/review\/([^/]+)$/);
|
||||
|
|
@ -68,6 +69,7 @@ const server = http.createServer(async (req, res) => {
|
|||
}
|
||||
const price = url.pathname.match(/^\/customers\/([^/]+)\/pricing$/);
|
||||
if (req.method === 'PUT' && price) {
|
||||
if (!(await denyOrRedirect(req, res, json, { permission: 'sales.quote' }))) return;
|
||||
const chunks = [];
|
||||
for await (const c of req) chunks.push(c);
|
||||
const payload = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue