Sicurezza e Autenticazione

5 min di lettura

Sicurezza e Autenticazione

PgArachne si affida interamente al sistema di permessi di PostgreSQL. Non reinventa le Liste di Controllo degli Accessi (ACL). PgArachne supporta quattro metodi di autenticazione. I metodi 1–3 usano SET LOCAL ROLE per cambiare identità; il metodo 4 (credenziali dirette) si connette direttamente come l’utente, quindi non è necessario alcun cambio di ruolo.

Questo vale per gli endpoint JSON-RPC e MCP. L’endpoint SSE è l’unica eccezione — autentica il chiamante ma non cambia ruolo né verifica i permessi per canale, poiché i canali LISTEN/NOTIFY di PostgreSQL non sono oggetti del database con propri GRANT da far rispettare. Consulta la pagina SSE per capire cosa significa in pratica.

1. Login Interattivo (JWT)

Gli utenti si autenticano utilizzando il loro reale nome utente e password PostgreSQL con autenticazione HTTP Basic su POST /{prefix}/{database}/token. In caso di successo, ricevono un JWT di breve durata. Le richieste successive trasportano questo token nell’header Authorization: Bearer <token>, e PgArachne cambia il ruolo attivo in quell’utente per la durata di ogni richiesta.

curl -X POST http://localhost:8080/db/my_database/token -u demo_user:user_password
→ {"token":"eyJhbGciOi...","token_type":"Bearer","expires_in":28800}

Le credenziali viaggiano nell’header Authorization, mai in un corpo JSON, e il login ha un proprio URL così che un reverse proxy possa limitarne la frequenza senza leggere i corpi delle richieste. La risposta non è mai memorizzabile nella cache.

Richiede JWT_SECRET. Se non è configurato, l’endpoint restituisce HTTP 404 (-32601) e i client usano invece le credenziali dirette (metodo 4) o i token API (metodo 2).

2. Account di Servizio (Token API)

Per sistemi automatizzati o script, puoi utilizzare token API a lunga durata.

  • I token sono memorizzati nella tabella pgarachne.api_tokens.
  • Ogni token è mappato a un utente/ruolo database specifico.
  • Invia il token tramite l’header Authorization: Bearer <token>.

La creazione di token API richiede pgarachne_admin. Usa pgarachne.add_api_token(...) con un ruolo membro di pgarachne_admin.

3. Identity provider esterno (JWT personalizzato)

Se autentichi gli utenti fuori da PgArachne (ad esempio con un servizio di autenticazione esterno), puoi generare lì i JWT e inviarli direttamente a PgArachne.

  • Formato header: Authorization: Bearer <jwt>.
  • Firma: solo HMAC (HS256/HS384/HS512) con lo stesso JWT_SECRET configurato in PgArachne.
  • Claim obbligatori: db_role (stringa non vuota) e db_name (stringa che deve corrispondere a /db/:database).
  • Claim obbligatorio: exp (timestamp Unix) per la scadenza del token.

Richiede JWT_SECRET; senza di esso, i valori Bearer vengono verificati solo come token API.

Esempio minimo di payload:

{
  "db_role": "demo_user",
  "db_name": "my_database",
  "exp": 1767225600
}

Importante: gli algoritmi JWT asimmetrici (ad esempio RS256/ES256) non sono accettati dall’implementazione attuale del server.

4. Credenziali dirette del database (Basic Auth)

L’opzione più semplice: invia il nome utente e la password PostgreSQL con ogni richiesta usando la normale autenticazione HTTP Basic. PgArachne apre un pool di connessioni dedicato, autenticato direttamente come quell’utente — SET LOCAL ROLE non viene eseguito.

Authorization: Basic <base64(username:password)>

Esempio con curl:

curl -X POST http://localhost:8080/db/my_database/jsonrpc \
  -u demo_user:user_password \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","method":"api.hello_world","params":{},"id":1}'
Quando usare le credenziali dirette:
  • Prototipazione rapida e sviluppo locale — non è richiesta alcuna gestione dei token.
  • Servizi interni che già gestiscono le credenziali in modo sicuro.
  • Scenari in cui l’emissione anticipata di un JWT aggiunge complessità non necessaria.

Differenze principali rispetto ai metodi basati su token:

  • Non è richiesto alcun GRANT demo_user TO pgarachne — PgArachne non cambia ruolo.
  • La Row-Level Security di PostgreSQL e i permessi GRANT vengono applicati normalmente perché la connessione viene eseguita come l’utente reale.
  • I pool di connessioni sono mantenuti per utente per efficienza, con una durata massima di 5 minuti. Dopo un cambio di password, le vecchie connessioni scadono entro quella finestra temporale.
  • Perché le chiavi di idempotenza funzionino con le credenziali dirette, concedi all’utente EXECUTE ON FUNCTION pgarachne.save_idempotency_key(text, text).

Privilegi Proxy: richiesti solo per i metodi 1–3

Per l’autenticazione JWT e a token API, PgArachne si connette come l’utente di sistema definito in DB_USER (ad es. pgarachne) e cambia identità tramite SET LOCAL ROLE. L’utente di sistema deve essere membro di ogni ruolo di destinazione.

-- Richiesto solo per l'autenticazione JWT / a token API:
GRANT demo_user TO pgarachne;

Questo grant non è necessario quando si usano le credenziali dirette (metodo 4).

Confronto dei metodi di autenticazione

MetodoHeaderSET LOCAL ROLEGRANT … TO pgarachneIdeale per
JWT (/token)Bearer <jwt>✅ SìRichiestoSessioni utente finale
Token APIBearer <token>✅ SìRichiestoServizi automatizzati
JWT esternoBearer <jwt>✅ SìRichiestoIntegrazione con identity provider esterni
Credenziali diretteBasic <b64>❌ NoNon richiestoSviluppo, servizi interni

Quali funzioni sono richiamabili

PgArachne non ha una propria allowlist di schemi: ciò che un ruolo può chiamare è deciso interamente dai privilegi di PostgreSQL. Una funzione è richiamabile (ed elencata da capabilities, da MCP tools/list e dall’export OpenAPI) quando accetta un singolo parametro jsonb, il ruolo ha EXECUTE su di essa e il ruolo ha USAGE sul suo schema. Le funzioni in pg_catalog, information_schema, pgarachne e quelle appartenenti a estensioni sono escluse dall’elenco per mantenerlo leggibile, ma valgono per loro gli stessi privilegi.

I valori predefiniti di PostgreSQL sono permissivi

Per impostazione predefinita PostgreSQL concede EXECUTE su ogni nuova funzione a PUBLIC, e ogni ruolo ha USAGE sullo schema public. Una funzione di supporto public.something(jsonb) è quindi richiamabile tramite l’API da qualsiasi ruolo autenticato, a meno che non si revochi il privilegio. Configurazione consigliata:

-- Le nuove funzioni non sono eseguibili da tutti (eseguire come il ruolo che le crea):
ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC;

-- Tenere le funzioni API in uno schema dedicato e concedere i privilegi esplicitamente:
GRANT USAGE ON SCHEMA api TO demo_user;
GRANT EXECUTE ON FUNCTION api.hello_world(jsonb) TO demo_user;

Protezione dal brute force

LOGIN_RATE_LIMIT e LOGIN_RATE_LIMIT_PER_IP si applicano a ogni metodo di autenticazione, su ogni endpoint (JSON-RPC, MCP, SSE, OpenAPI):

  • /token: ogni tentativo viene conteggiato nei limiti per (IP, nome utente) e per IP.
  • Credenziali dirette (Basic Auth): i tentativi falliti vengono conteggiati nei limiti per (IP, nome utente) e per IP. Le richieste riuscite non vengono conteggiate, quindi un client legittimo che invia le credenziali a ogni richiesta non viene mai limitato.
  • Token Bearer (JWT o token API): i tentativi falliti vengono conteggiati nel limite per IP.

Una volta esaurito un limite, gli ulteriori tentativi vengono rifiutati con HTTP 429 finché non trascorre la finestra (LOGIN_RATE_WINDOW) — senza nemmeno verificare le credenziali.