dd24257640
- /api/vault proxy now injects X-Vault-Token: the proxy declared its request
hook with http-proxy-middleware v3 syntax (on: { proxyReq }), which the
installed HPM v2 silently ignores — so every vault call reached OpenBao
unauthenticated (the recurring 403). Rewritten as v2 onProxyReq.
- Header injection ordered before fixRequestBody (the body write flushes
headers; setting X-Vault-Token after it failed on every POST/PUT).
- initORM add-only schema heal: sequelize.sync() never ALTERs, so newer columns
(PluginInstance.lastLog) are now added via describeTable + addColumn.
- Long-lived external-app tokens via sso-app role (768h periodic); VaultAppToken
stores each app token's accessor and renews it at boot + every 6h; re-minting
revokes the previous token via its accessor.
- Wire-level tests for the vault proxy + app-token accessor lifecycle.
- package.json + lockfile bumped to 1.23.0.
Co-Authored-By: Claude <noreply@anthropic.com>
44 lines
1.9 KiB
JavaScript
44 lines
1.9 KiB
JavaScript
'use strict';
|
|
|
|
// VaultAppToken — the ACCESSOR of an OpenBao token minted for an external app
|
|
// from the vault UI (Apps tab), so sso can keep the token alive.
|
|
//
|
|
// The token itself is shown ONCE at mint and never stored (a stolen accessor
|
|
// cannot authenticate — it can only look up, renew, or revoke its token, and
|
|
// only the sso broker's policy grants those endpoints). App tokens are minted
|
|
// through the sso-app role as PERIODIC tokens: they live forever, but only if
|
|
// something renews them inside every period window. That something is sso's
|
|
// renewal loop (vault_broker.startAppTokenRenewal), which walks these rows and
|
|
// POSTs auth/token/renew-accessor on a timer — so a downstream app's credential
|
|
// stays valid as long as sso itself is running, with no renewal code needed in
|
|
// the downstream app.
|
|
//
|
|
// One row per app name: re-minting an app's token revokes the previous token
|
|
// via its accessor (no zombie credentials) and replaces the row.
|
|
|
|
const { Model } = require('@simpleworkjs/orm');
|
|
|
|
class VaultAppToken extends Model {
|
|
static fields = {
|
|
id: { type: 'uuid', primaryKey: true },
|
|
// The external app's name — also its policy (app-<name>) and KV namespace
|
|
// (secret/apps/<name>/). Unique: one live token per app.
|
|
name: { type: 'string', isRequired: true, unique: true, min: 1, max: 64 },
|
|
// The minted token's accessor (renew/revoke handle, cannot authenticate).
|
|
accessor: { type: 'string', isRequired: true, max: 128 },
|
|
// Renewal bookkeeping, updated by the renewal loop.
|
|
lastRenewedAt: { type: 'integer' },
|
|
lastError: { type: 'text' },
|
|
// Audit stamps (set by the route handler, not by an ORM hook).
|
|
created_by: { type: 'string' },
|
|
created_on: { type: 'integer' },
|
|
};
|
|
|
|
static async getByName(name) {
|
|
const rows = await this.list({ where: { name } });
|
|
return rows[0] || null;
|
|
}
|
|
}
|
|
|
|
module.exports = { VaultAppToken };
|