Passa al contenuto principale

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:

  1. Ruolo principale sull'utente (user_accounts.role) → mappato a un set base di permission_key tramite la tabella role_permissions.
  2. Override puntuali sul singolo utente tramite user_extra_permissions che 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.read
  • companies.write
  • companies.delete
  • companies.stop (sospensione operativa, non delete)
  • companies.balance.adjust (azione finanziaria, MFA re-challenge)

3.2 Accounts​

  • accounts.read
  • accounts.write
  • accounts.delete (soft delete amministrativo, MFA re-challenge)

3.3 Originators​

  • originators.read
  • originators.write
  • originators.delete
  • originators.activate

3.4 Terminators​

  • terminators.read
  • terminators.write
  • terminators.delete
  • terminators.health_test

3.5 Devices​

  • devices.read
  • devices.write
  • devices.delete
  • devices.sip_password.set (azione sensibile, MFA re-challenge consigliato)

3.6 Tariffs​

  • tariffs.read
  • tariffs.write
  • tariffs.delete
  • tariffs.upload_csv
  • tariffs.send_mail
  • tariffs.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.read
  • destinations.write
  • destinations.delete
  • destinations.import

3.8 Routing​

  • routing.read
  • routing.write
  • routing.failover_policy.edit
  • routing.access_policy.edit

3.9 Balance & Finance​

  • balance.read
  • billing.read
  • billing.read.all
  • invoices.read
  • invoices.create
  • invoices.send
  • payments.read
  • payments.create
  • payments.reconcile
  • expenses.read
  • expenses.write

3.10 Reports​

  • 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

3.11 Pattern Analyzer (fraud)​

  • fraud.read
  • fraud.run_analysis
  • fraud.block_cli
  • fraud.block_destination
  • pattern.read
  • pattern.run

3.12 NOC TT (Quality Verifier + Tickets)​

  • quality_alerts.read
  • quality_alerts.write
  • noc.read
  • noctt.read
  • noctt.run_test
  • noctt.recording.listen
  • noctt.ticket.send
  • benchmark.run

3.13 Settings​

  • settings.read
  • settings.write
  • settings.system
  • settings.delete
  • settings.users.manage
  • settings.mailboxes.manage
  • settings.email.config
  • settings.bank.config
  • settings.telegram.config

3.14 Secrets Vault​

  • secrets_vault.read
  • secrets_vault.rotate (azione sensibile, MFA re-challenge obbligatorio)
  • secrets_vault.delete (azione sensibile, MFA re-challenge obbligatorio)

3.15 Agents (cap. 30)​

  • agents.read
  • agents.write
  • agents.fee_rules.edit
  • agents.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.manage
  • admin.ai_agent.manage
  • admin.users.manage
  • admin.bulk_import
  • admin.auto_upload.destinations

3.18 Wizard​

  • wizard.use

3.19 System​

  • system.audit.read
  • system.users.manage
  • system.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.

Permissionadminoperatornocfinanceagentsales
audit.read✓–✓–––
companies.read✓✓✓✓✓*✓
companies.write✓✓–––✓
companies.deleteMFA–––––
companies.stop✓✓✓–––
companies.balance.adjustMFA––MFA––
accounts.read✓–✓–––
accounts.write✓–––––
accounts.deleteMFA–––––
originators.read✓✓✓✓–✓
originators.write✓✓––––
originators.deleteMFA–––––
originators.activate✓✓✓–––
terminators.read✓✓✓✓–✓
terminators.write✓✓––––
terminators.deleteMFA–––––
terminators.health_test✓✓✓–––
devices.read✓✓✓––✓
devices.write✓✓––––
devices.deleteMFA–––––
devices.sip_password.setMFAMFA––––
tariffs.read✓✓✓✓✓*✓
tariffs.write✓✓––––
tariffs.deleteMFA–––––
tariffs.upload_csv✓✓––––
tariffs.send_mail✓✓–––✓
tariffs.rebill.runMFA–––––
destinations.read✓✓✓✓–✓
destinations.write✓✓––––
destinations.deleteMFA–––––
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.deleteMFA–––––
settings.users.manageMFA–––––
settings.mailboxes.manage✓–––––
settings.email.config✓–––––
settings.bank.configMFA––MFA––
settings.telegram.config✓–––––
secrets_vault.read✓–––––
secrets_vault.rotateMFA–––––
secrets_vault.deleteMFA–––––
agents.read✓––✓✓*✓
agents.write✓–––––
agents.fee_rules.edit✓––✓––
agents.payouts.runMFA––MFA––
agentcore.tool.read_only✓✓✓✓––
agentcore.tool.write✓✓✓†–––
agentcore.tool.destructiveMFA–––––
admin.agents.manage✓–––––
admin.ai_agent.manage✓–––––
admin.users.manage✓–––––
admin.bulk_import✓–––––
admin.auto_upload.destinations✓–––––
wizard.use✓✓–––✓
system.audit.read✓–✓✓––
system.users.manageMFA–––––
system.maintenanceMFA–––––

Note:

  • ✓* per agent: filtro app-level che restringe ai soli company_id posseduti.
  • ✓† per noc su agentcore.tool.write: limitato a tool fraud-related (block_cli, block_destination); per tutto il resto vale read_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.

Permissioncustomer_admincustomer_usercustomer_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.adjust
  • tariffs.rebill.run
  • agents.payouts.run
  • settings.users.manage, system.users.manage, system.maintenance
  • settings.bank.config
  • devices.sip_password.set
  • secrets_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: operator e il ruolo Operations interno Akira, responsabile di client onboarding, routing e supervisione del traffico. Visibilita su 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 ruolo noc.

8.2 Tickets condivide noctt.read con NOC TT​

  • I moduli /tickets e /noc-tt (frontend) e i router routers/tickets.py, routers/reports/quality.py (backend) richiedono tutti noctt.read. Non esiste un permesso tickets.read separato.
  • 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.