132 lines
5.4 KiB
JavaScript
132 lines
5.4 KiB
JavaScript
|
|
// Journey: admin RBAC end-to-end (login admin → roles CRUD → assign → reload → verify → cleanup)
|
||
|
|
//
|
||
|
|
// Endpoints exercised (happy path, admin role required):
|
||
|
|
// POST /api/v1/auth/login (admin)
|
||
|
|
// GET /api/v1/permissions/roles
|
||
|
|
// POST /api/v1/permissions/roles
|
||
|
|
// PATCH /api/v1/permissions/roles/:id
|
||
|
|
// GET /api/v1/permissions/roles/:id/permissions
|
||
|
|
// PUT /api/v1/permissions/roles/:id/permissions
|
||
|
|
// GET /api/v1/permissions/catalog (to pick a permission_id)
|
||
|
|
// POST /api/v1/permissions/users/:uid/roles
|
||
|
|
// GET /api/v1/permissions/users/:uid/roles
|
||
|
|
// POST /api/v1/permissions/policy/reload
|
||
|
|
// GET /api/v1/permissions/me (as the assigned user)
|
||
|
|
// DELETE /api/v1/permissions/users/:uid/roles/:role_id
|
||
|
|
// DELETE /api/v1/permissions/roles/:id
|
||
|
|
//
|
||
|
|
// PREREQUISITE — admin user seeded by `make k6-seed-admin`. ADMIN_EMAIL,
|
||
|
|
// ADMIN_PASSWORD env vars must be set (k6.env exports them). If unset the
|
||
|
|
// journey prints a skip notice and exits 0 (covered by smoke/permission_admin
|
||
|
|
// for route-existence checks).
|
||
|
|
import { sleep } from 'k6';
|
||
|
|
import { get, post, put, patch, del, checkEnvelope } from '../lib/http.js';
|
||
|
|
import { login } from '../lib/auth.js';
|
||
|
|
import { registerAndConfirm } from '../lib/auth.js';
|
||
|
|
import { cfg, unique } from '../lib/config.js';
|
||
|
|
|
||
|
|
export const options = {
|
||
|
|
vus: 1,
|
||
|
|
iterations: 1,
|
||
|
|
thresholds: { checks: ['rate==1.0'] },
|
||
|
|
};
|
||
|
|
|
||
|
|
function pickAnyPermissionID(bearer) {
|
||
|
|
const cat = checkEnvelope(get('/api/v1/permissions/catalog', bearer), 'GET /permissions/catalog').data;
|
||
|
|
const list = (cat && (cat.list || cat.tree)) || [];
|
||
|
|
// Find a leaf — node with http_methods set.
|
||
|
|
const stack = [...list];
|
||
|
|
while (stack.length) {
|
||
|
|
const n = stack.shift();
|
||
|
|
if (n.http_methods && n.id) return n.id;
|
||
|
|
if (n.children) for (const c of n.children) stack.push(c);
|
||
|
|
}
|
||
|
|
if (list.length > 0 && list[0].id) return list[0].id;
|
||
|
|
throw new Error('catalog: no permissions found');
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function () {
|
||
|
|
if (!cfg.adminEmail || (!cfg.adminPassword && !cfg.adminAccessToken)) {
|
||
|
|
console.log('[rbac_admin] skipped: ADMIN_EMAIL + (ADMIN_PASSWORD or ADMIN_ACCESS_TOKEN) env not set. Run `make k6-seed-admin` first.');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// 1. obtain admin access_token. Prefer ADMIN_ACCESS_TOKEN (issued by
|
||
|
|
// k6-seed-admin at registration time) because ZITADEL v2 disables OAuth
|
||
|
|
// password grant by default, so /auth/login → VerifyPassword returns 502.
|
||
|
|
let adminAccessToken = cfg.adminAccessToken;
|
||
|
|
if (!adminAccessToken) {
|
||
|
|
const admin = login({ email: cfg.adminEmail, password: cfg.adminPassword });
|
||
|
|
adminAccessToken = admin.access_token;
|
||
|
|
}
|
||
|
|
const adminBearer = { Authorization: `Bearer ${adminAccessToken}` };
|
||
|
|
|
||
|
|
// 2. register a target user (we'll assign a role to them and verify via /me)
|
||
|
|
const { tokens: target } = registerAndConfirm();
|
||
|
|
const targetBearer = { Authorization: `Bearer ${target.access_token}` };
|
||
|
|
|
||
|
|
// 3. list roles (sanity)
|
||
|
|
const rolesBefore = checkEnvelope(get('/api/v1/permissions/roles', adminBearer), 'GET /roles').data;
|
||
|
|
if (!rolesBefore || !Array.isArray(rolesBefore.roles)) throw new Error('roles list bad shape');
|
||
|
|
|
||
|
|
// 4. create a role
|
||
|
|
const roleKey = unique('k6role').replace(/[^a-z0-9_]/g, '_');
|
||
|
|
const created = checkEnvelope(
|
||
|
|
post('/api/v1/permissions/roles', { key: roleKey, display_name: 'k6 RBAC role' }, adminBearer),
|
||
|
|
'POST /roles',
|
||
|
|
).data;
|
||
|
|
const roleID = created.id;
|
||
|
|
|
||
|
|
// 5. patch role display name
|
||
|
|
const newDisplay = `${created.display_name} (patched)`;
|
||
|
|
checkEnvelope(
|
||
|
|
patch(`/api/v1/permissions/roles/${roleID}`, { display_name: newDisplay }, adminBearer),
|
||
|
|
'PATCH /roles/:id',
|
||
|
|
);
|
||
|
|
|
||
|
|
// 6. get role permissions (empty initially)
|
||
|
|
checkEnvelope(get(`/api/v1/permissions/roles/${roleID}/permissions`, adminBearer), 'GET /roles/:id/permissions');
|
||
|
|
|
||
|
|
// 7. put role permissions (replace with one permission id picked from catalog)
|
||
|
|
const permID = pickAnyPermissionID(adminBearer);
|
||
|
|
checkEnvelope(
|
||
|
|
put(`/api/v1/permissions/roles/${roleID}/permissions`, { permission_ids: [permID] }, adminBearer),
|
||
|
|
'PUT /roles/:id/permissions',
|
||
|
|
);
|
||
|
|
|
||
|
|
// 8. assign role to target user
|
||
|
|
checkEnvelope(
|
||
|
|
post(`/api/v1/permissions/users/${target.uid}/roles`, { role_id: roleID }, adminBearer),
|
||
|
|
'POST /users/:uid/roles',
|
||
|
|
);
|
||
|
|
|
||
|
|
// 9. list target user's roles
|
||
|
|
const targetRoles = checkEnvelope(
|
||
|
|
get(`/api/v1/permissions/users/${target.uid}/roles`, adminBearer),
|
||
|
|
'GET /users/:uid/roles',
|
||
|
|
).data;
|
||
|
|
const hasRole = targetRoles.user_roles && targetRoles.user_roles.some((ur) => ur.role_id === roleID);
|
||
|
|
if (!hasRole) throw new Error('assigned role not visible in user_roles');
|
||
|
|
|
||
|
|
// 10. policy reload
|
||
|
|
checkEnvelope(post('/api/v1/permissions/policy/reload', {}, adminBearer), 'POST /policy/reload');
|
||
|
|
|
||
|
|
// give the gateway a moment for the pub/sub reload to settle
|
||
|
|
sleep(0.5);
|
||
|
|
|
||
|
|
// 11. target /permissions/me reflects the role
|
||
|
|
const me = checkEnvelope(get('/api/v1/permissions/me', targetBearer), 'GET /permissions/me').data;
|
||
|
|
if (!me.roles || !me.roles.includes(roleKey)) {
|
||
|
|
throw new Error(`/permissions/me did not include role ${roleKey}: got=${JSON.stringify(me.roles)}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
// 12. revoke role
|
||
|
|
checkEnvelope(
|
||
|
|
del(`/api/v1/permissions/users/${target.uid}/roles/${roleID}`, null, adminBearer),
|
||
|
|
'DELETE /users/:uid/roles/:role_id',
|
||
|
|
);
|
||
|
|
|
||
|
|
// 13. delete role (now safe because no user is assigned)
|
||
|
|
checkEnvelope(del(`/api/v1/permissions/roles/${roleID}`, null, adminBearer), 'DELETE /roles/:id');
|
||
|
|
}
|