RBAC Matrix — Akira
Riferimento documento: Akira v5.2, cap. 8.2 (Authentication & Authorization) Versione: 1.1 — 2026-05-21 Owner: Security Engineering
1. Sintesi
Akira adotta un modello RBAC a due livelli con override puntuali:
- Ruolo principale sull'utente (
user_accounts.role) → mappato a un set base dipermission_keytramite la tabellarole_permissions. - Override puntuali sul singolo utente tramite
user_extra_permissionsche possono sia concedere (grant) che negare (deny) un permesso rispetto al ruolo.
La risoluzione è in cascade: deny esplicito vince sempre su grant esplicito che vince
sul permesso di ruolo.
2. Schema DB di riferimento
-- Ruolo principale, vincolato a un set chiuso
ALTER TABLE user_accounts
ADD COLUMN role VARCHAR(32) NOT NULL DEFAULT 'sales'
CHECK (role IN ('super_admin','admin','operator','noc','finance','agent','sales',
'customer_admin','customer_user','customer_viewer'));
-- Matrice base ruolo → permission
CREATE TABLE role_permissions (
role VARCHAR(32) NOT NULL,
permission_key VARCHAR(96) NOT NULL,
PRIMARY KEY (role, permission_key)
);
-- Override puntuali (grant o deny) sul singolo utente
CREATE TABLE user_extra_permissions (
id BIGSERIAL PRIMARY KEY,
user_account_id BIGINT NOT NULL REFERENCES user_accounts(id) ON DELETE CASCADE,
permission_key VARCHAR(96) NOT NULL,
grant_or_deny VARCHAR(8) NOT NULL CHECK (grant_or_deny IN ('grant','deny')),
granted_by BIGINT REFERENCES user_accounts(id),
granted_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NULL,
reason TEXT,
UNIQUE (user_account_id, permission_key)
);
CREATE INDEX idx_user_extra_perms_user ON user_extra_permissions(user_account_id);
Funzione di risoluzione
CREATE OR REPLACE FUNCTION user_has_permission(p_user_id BIGINT, p_key VARCHAR)
RETURNS BOOLEAN LANGUAGE sql STABLE AS $$
WITH override AS (
SELECT grant_or_deny FROM user_extra_permissions
WHERE user_account_id = p_user_id
AND permission_key = p_key
AND (expires_at IS NULL OR expires_at > now())
)
SELECT CASE
WHEN (SELECT grant_or_deny FROM override) = 'deny' THEN FALSE
WHEN (SELECT grant_or_deny FROM override) = 'grant' THEN TRUE
ELSE EXISTS (
SELECT 1 FROM role_permissions rp
JOIN user_accounts ua ON ua.role = rp.role
WHERE ua.id = p_user_id AND rp.permission_key = p_key
)
END;
$$;
3. Permission keys (98)
Naming convention: <modulo>.<oggetto?>.<azione>. Azione sensibile esplicitata.
3.1 Audit
audit.read
3.2 Companies
companies.readcompanies.writecompanies.deletecompanies.stop(sospensione operativa, non delete)companies.balance.adjust(azione finanziaria, MFA re-challenge)
3.2 Accounts
accounts.readaccounts.writeaccounts.delete(soft delete amministrativo, MFA re-challenge)
3.3 Originators
originators.readoriginators.writeoriginators.deleteoriginators.activate
3.4 Terminators
terminators.readterminators.writeterminators.deleteterminators.health_test
3.5 Devices
devices.readdevices.writedevices.deletedevices.sip_password.set(azione sensibile, MFA re-challenge consigliato)
3.6 Tariffs
tariffs.readtariffs.writetariffs.deletetariffs.upload_csvtariffs.send_mailtariffs.rebill.run(rebill markup reseller — clona tariffe, MFA re-challenge obbligatorio)tariffs.rebill.dry_run(rebill CDR storici: crea task + dry-run, nessuna scrittura sui CDR)tariffs.rebill.apply(rebill CDR storici: applica il re-rating ai CDR, MFA re-challenge obbligatorio)tariffs.rebill.rollback(rebill CDR storici: ripristina i CDR dall'audit trail, MFA re-challenge obbligatorio)
3.7 Destinations
destinations.readdestinations.writedestinations.deletedestinations.import
3.8 Routing
routing.readrouting.writerouting.failover_policy.editrouting.access_policy.edit
3.9 Balance & Finance
balance.readbilling.readbilling.read.allinvoices.readinvoices.createinvoices.sendpayments.readpayments.createpayments.reconcileexpenses.readexpenses.write
3.10 Reports
reports.cdr.readreports.readreports.admincdr.readcdr.read_unmaskedcdr.recording.readcdr.raw.readcdr.exportreports.active_calls.readlive_calls.readreports.summary.readreports.export
3.11 Pattern Analyzer (fraud)
fraud.readfraud.run_analysisfraud.block_clifraud.block_destinationpattern.readpattern.run
3.12 NOC TT (Quality Verifier + Tickets)
quality_alerts.readquality_alerts.writenoc.readnoctt.readnoctt.run_testnoctt.recording.listennoctt.ticket.sendbenchmark.run
3.13 Settings
settings.readsettings.writesettings.systemsettings.deletesettings.users.managesettings.mailboxes.managesettings.email.configsettings.bank.configsettings.telegram.config
3.14 Secrets Vault
secrets_vault.readsecrets_vault.rotate(azione sensibile, MFA re-challenge obbligatorio)secrets_vault.delete(azione sensibile, MFA re-challenge obbligatorio)
3.15 Agents (cap. 30)
agents.readagents.writeagents.fee_rules.editagents.payouts.run
3.16 AgentCore tools (cap. 48)
agentcore.tool.read_only(query_, report_, health_check_*)agentcore.tool.write(create_, update_, activate_*)agentcore.tool.destructive(delete_, block_, disable_*) — MFA re-challenge
3.17 Admin tools
admin.agents.manageadmin.ai_agent.manageadmin.users.manageadmin.bulk_importadmin.auto_upload.destinations
3.18 Wizard
wizard.use
3.19 System
system.audit.readsystem.users.managesystem.maintenance
4. Matrice ruolo × permission
super_admin è un ruolo persistito separato da admin e, finché non esistono
permission system-owner dedicate, eredita l'intero set di permission di admin.
Legenda: ✓ concesso; – non concesso; MFA concesso ma richiede MFA re-challenge.
| Permission | admin | operator | noc | finance | agent | sales |
|---|---|---|---|---|---|---|
| audit.read | ✓ | – | ✓ | – | – | – |
| companies.read | ✓ | ✓ | ✓ | ✓ | ✓* | ✓ |
| companies.write | ✓ | ✓ | – | – | – | ✓ |
| companies.delete | MFA | – | – | – | – | – |
| companies.stop | ✓ | ✓ | ✓ | – | – | – |
| companies.balance.adjust | MFA | – | – | MFA | – | – |
| accounts.read | ✓ | – | ✓ | – | – | – |
| accounts.write | ✓ | – | – | – | – | – |
| accounts.delete | MFA | – | – | – | – | – |
| originators.read | ✓ | ✓ | ✓ | ✓ | – | ✓ |
| originators.write | ✓ | ✓ | – | – | – | – |
| originators.delete | MFA | – | – | – | – | – |
| originators.activate | ✓ | ✓ | ✓ | – | – | – |
| terminators.read | ✓ | ✓ | ✓ | ✓ | – | ✓ |
| terminators.write | ✓ | ✓ | – | – | – | – |
| terminators.delete | MFA | – | – | – | – | – |
| terminators.health_test | ✓ | ✓ | ✓ | – | – | – |
| devices.read | ✓ | ✓ | ✓ | – | – | ✓ |
| devices.write | ✓ | ✓ | – | – | – | – |
| devices.delete | MFA | – | – | – | – | – |
| devices.sip_password.set | MFA | MFA | – | – | – | – |
| tariffs.read | ✓ | ✓ | ✓ | ✓ | ✓* | ✓ |
| tariffs.write | ✓ | ✓ | – | – | – | – |
| tariffs.delete | MFA | – | – | – | – | – |
| tariffs.upload_csv | ✓ | ✓ | – | – | – | – |
| tariffs.send_mail | ✓ | ✓ | – | – | – | ✓ |
| tariffs.rebill.run | MFA | – | – | – | – | – |
| destinations.read | ✓ | ✓ | ✓ | ✓ | – | ✓ |
| destinations.write | ✓ | ✓ | – | – | – | – |
| destinations.delete | MFA | – | – | – | – | – |
| destinations.import | ✓ | ✓ | – | – | – | – |
| routing.read | ✓ | ✓ | ✓ | – | – | – |
| routing.write | ✓ | ✓ | – | – | – | – |
| routing.failover_policy.edit | ✓ | ✓ | – | – | – | – |
| routing.access_policy.edit | ✓ | ✓ | – | – | – | – |
| balance.read | ✓ | ✓ | – | ✓ | ✓* | – |
| billing.read | ✓ | ✓ | – | ✓ | ✓* | – |
| billing.read.all | ✓ | ✓ | – | ✓ | – | – |
| invoices.read | ✓ | – | – | ✓ | – | – |
| invoices.create | ✓ | – | – | ✓ | – | – |
| invoices.send | ✓ | – | – | ✓ | – | – |
| payments.read | ✓ | – | – | ✓ | – | – |
| payments.create | ✓ | – | – | ✓ | – | – |
| payments.reconcile | ✓ | – | – | ✓ | – | – |
| expenses.read | ✓ | – | – | ✓ | – | – |
| expenses.write | ✓ | – | – | ✓ | – | – |
| reports.cdr.read | ✓ | ✓ | ✓ | ✓ | ✓* | ✓ |
| reports.read | ✓ | ✓ | – | ✓ | – | – |
| reports.admin | ✓ | ✓ | – | ✓ | – | – |
| cdr.read | ✓ | ✓ | ✓ | ✓ | – | – |
| cdr.read_unmasked | ✓ | – | ✓ | – | – | – |
| cdr.recording.read | ✓ | – | – | – | – | – |
| cdr.raw.read | ✓ | – | – | – | – | – |
| cdr.export | ✓ | – | ✓ | ✓ | – | – |
| reports.active_calls.read | ✓ | ✓ | ✓ | – | – | ✓ |
| live_calls.read | ✓ | ✓ | ✓ | – | – | – |
| reports.summary.read | ✓ | ✓ | ✓ | ✓ | ✓* | ✓ |
| reports.export | ✓ | ✓ | ✓ | ✓ | – | ✓ |
| fraud.read | ✓ | ✓ | ✓ | – | – | – |
| fraud.run_analysis | ✓ | – | ✓ | – | – | – |
| fraud.block_cli | ✓ | – | ✓ | – | – | – |
| fraud.block_destination | ✓ | – | ✓ | – | – | – |
| pattern.read | ✓ | ✓ | ✓ | – | – | – |
| pattern.run | ✓ | – | ✓ | – | – | – |
| quality_alerts.read | ✓ | ✓ | ✓ | – | – | – |
| quality_alerts.write | ✓ | – | ✓ | – | – | – |
| noc.read | ✓ | ✓ | ✓ | – | – | – |
| noctt.read | ✓ | ✓ | ✓ | – | – | – |
| noctt.run_test | ✓ | – | ✓ | – | – | – |
| noctt.recording.listen | ✓ | – | ✓ | – | – | – |
| noctt.ticket.send | ✓ | – | ✓ | – | – | – |
| benchmark.run | ✓ | ✓ | ✓ | – | – | – |
| settings.read | ✓ | ✓ | ✓ | ✓ | – | – |
| settings.write | ✓ | – | – | – | – | – |
| settings.system | ✓ | – | – | – | – | – |
| settings.delete | MFA | – | – | – | – | – |
| settings.users.manage | MFA | – | – | – | – | – |
| settings.mailboxes.manage | ✓ | – | – | – | – | – |
| settings.email.config | ✓ | – | – | – | – | – |
| settings.bank.config | MFA | – | – | MFA | – | – |
| settings.telegram.config | ✓ | – | – | – | – | – |
| secrets_vault.read | ✓ | – | – | – | – | – |
| secrets_vault.rotate | MFA | – | – | – | – | – |
| secrets_vault.delete | MFA | – | – | – | – | – |
| agents.read | ✓ | – | – | ✓ | ✓* | ✓ |
| agents.write | ✓ | – | – | – | – | – |
| agents.fee_rules.edit | ✓ | – | – | ✓ | – | – |
| agents.payouts.run | MFA | – | – | MFA | – | – |
| agentcore.tool.read_only | ✓ | ✓ | ✓ | ✓ | – | – |
| agentcore.tool.write | ✓ | ✓ | ✓† | – | – | – |
| agentcore.tool.destructive | MFA | – | – | – | – | – |
| admin.agents.manage | ✓ | – | – | – | – | – |
| admin.ai_agent.manage | ✓ | – | – | – | – | – |
| admin.users.manage | ✓ | – | – | – | – | – |
| admin.bulk_import | ✓ | – | – | – | – | – |
| admin.auto_upload.destinations | ✓ | – | – | – | – | – |
| wizard.use | ✓ | ✓ | – | – | – | ✓ |
| system.audit.read | ✓ | – | ✓ | ✓ | – | – |
| system.users.manage | MFA | – | – | – | – | – |
| system.maintenance | MFA | – | – | – | – | – |
Note:
✓*peragent: filtro app-level che restringe ai solicompany_idposseduti.✓†pernocsuagentcore.tool.write: limitato a tool fraud-related (block_cli,block_destination); per tutto il resto valeread_only.
4.1 Customer tenant roles
I ruoli customer sono scaffold GA per il customer portal e richiedono
user_accounts.company_id valorizzato. Lo scope tenant viene applicato dal
backend tramite request.state.tenant_scope.
| Permission | customer_admin | customer_user | customer_viewer |
|---|---|---|---|
| companies.read | ✓ | ✓ | ✓ |
| balance.read | ✓ | ✓ | ✓ |
| billing.read | ✓ | ✓ | ✓ |
| reports.cdr.read | ✓ | ✓ | ✓ |
| reports.read | ✓ | ✓ | ✓ |
| reports.admin | – | – | – |
| cdr.read | ✓ | ✓ | ✓ |
| cdr.export | ✓ | – | – |
| reports.summary.read | ✓ | ✓ | ✓ |
| reports.export | ✓ | – | – |
5. Confirmation flow (MFA re-challenge)
Per le azioni marcate MFA la UI/API impone uno step aggiuntivo di MFA re-challenge
(TOTP o recovery code) prima dell'esecuzione, anche se la sessione corrente è già
autenticata. Lista esaustiva:
- Qualsiasi
*.delete - Qualsiasi
agentcore.tool.destructive companies.balance.adjusttariffs.rebill.runagents.payouts.runsettings.users.manage,system.users.manage,system.maintenancesettings.bank.configdevices.sip_password.setsecrets_vault.rotate,secrets_vault.delete
Backend: header X-MFA-Challenge-Token valido per 5 minuti dalla verifica TOTP.
Senza token valido → 403 mfa_required.
6. Implementazione FastAPI
6.1 Decorator
# apps/backend/akira_backend/core/permissions.py
from functools import wraps
from fastapi import HTTPException, Request
from .auth import get_current_user
from .db import db_session
SENSITIVE_PERMISSIONS = {
"companies.delete", "originators.delete", "terminators.delete",
"devices.delete", "tariffs.delete", "destinations.delete",
"agentcore.tool.destructive",
"companies.balance.adjust", "tariffs.rebill.run",
"agents.payouts.run", "settings.users.manage",
"system.users.manage", "system.maintenance", "settings.delete",
"settings.bank.config", "devices.sip_password.set",
}
def require_permission(permission_key: str):
def decorator(func):
@wraps(func)
async def wrapper(*args, request: Request, user=None, **kwargs):
user = user or await get_current_user(request)
async with db_session() as s:
allowed = await s.scalar(
"SELECT user_has_permission(:uid, :key)",
{"uid": user.id, "key": permission_key},
)
if not allowed:
raise HTTPException(403, f"permission_denied:{permission_key}")
if permission_key in SENSITIVE_PERMISSIONS:
token = request.headers.get("X-MFA-Challenge-Token")
if not await verify_mfa_challenge_token(user.id, token):
raise HTTPException(403, "mfa_required")
return await func(*args, request=request, user=user, **kwargs)
return wrapper
return decorator
6.2 Esempio uso su route
from akira_backend.core.permissions import require_permission
@router.delete("/companies/{id}")
@require_permission("companies.delete")
async def delete_company(id: int, user: User = Depends(get_current_user)):
...
6.3 Seed iniziale role_permissions
INSERT INTO role_permissions(role, permission_key) VALUES
('super_admin','companies.read'),
('super_admin','companies.write'),
('super_admin','companies.delete'),
('admin','companies.read'),
('admin','companies.write'),
('admin','companies.delete'),
-- … (tutte le permission, generate da fixture python)
('sales','companies.read'),
('sales','companies.write'),
('sales','tariffs.read'),
('sales','reports.cdr.read');
Generazione fixture: apps/backend/scripts/seed_rbac.py parsa questo documento
e produce le INSERT.
7. Audit
Ogni decisione di autorizzazione su permission MFA o *.delete viene loggata in
system_audit_log(user_id, permission_key, decision, resource, request_id, ts).
Retention minima: 2 anni.
8. Decisioni di mappa permessi
Le scelte che possono apparire arbitrarie nella matrice operator/noc sono documentate qui per fissare il razionale (riferimento: cap. 8.2 / 49.2).
8.1 Operator vede monitoring e report
- Razionale:
operatoreil ruolo Operations interno Akira, responsabile di client onboarding, routing e supervisione del traffico. Visibilitasu Active Calls (reports.active_calls.read,live_calls.read), Reports (reports.read,reports.admin,reports.export) e NOC TT (noctt.read,noc.read,quality_alerts.read,fraud.read,pattern.read) e` parte del set base necessario per il day-to-day operativo, non un privilegio elevato. - Operazioni sensibili sui ticket / quality alerts (
noctt.ticket.send,noctt.run_test,noctt.recording.listen,quality_alerts.write,fraud.run_analysis,fraud.block_*,pattern.run) restano riservate al ruolonoc.
8.2 Tickets condivide noctt.read con NOC TT
- I moduli
/ticketse/noc-tt(frontend) e i routerrouters/tickets.py,routers/reports/quality.py(backend) richiedono tuttinoctt.read. Non esiste un permessotickets.readseparato. - Razionale: come dichiarato nella sez. 3.12, il modulo NOC TT include sia il Quality Verifier che la gestione Tickets — sono due viste sulla stessa pipeline di incident management, quindi un unico gate di lettura coerente. Splittare i due permessi creerebbe stati incoerenti del tipo "vedo i ticket ma non l'alert che li ha generati".
- Conseguenza: tutti i ruoli che hanno
noctt.read(admin,super_admin,operator,noc) vedono entrambe le pagine; gli altri ruoli non le vedono. Questa e` la decisione presa in TASK-424 dopo i finding di re-audit AUD-058 / 068 / 077.