Skip to content

Security Hardening

Guida alla messa in sicurezza di un’applicazione costruita con Pragmatic.Design. Copre il modello di sicurezza di ogni modulo rilevante e le azioni che spettano al consumer.

Per segnalare una vulnerabilità: vedi SECURITY.md. Non aprire issue pubbliche.

I moduli Pragmatic adottano default fail-closed dove la sicurezza è in gioco:

  • Authorization — la DefaultEndpointPolicy a runtime è RequireAuthenticated; un endpoint senza policy esplicita richiede autenticazione.
  • Actions — la pipeline dei filtri (Validation → Permission → Policy → Resource) nega se un authorizer non concede; AnonymousUser ha NullUserAuthorization (deny-all).
  • Email — l’invio rifiuta di autenticarsi se UseSsl è richiesto ma TLS non è stato stabilito.
  • Migrations — le breaking change (DROP) sono bloccate salvo Force=true esplicito.
  • Signing key: minimo 32 byte (256 bit) per HMAC-SHA256 — validato a UseJwtAuthentication. Genera una chiave casuale, non una passphrase.
  • Issuer/Audience: in ambiente Production devono essere entrambi configurati — altrimenti UseJwtAuthentication lancia. Senza, il validatore accetterebbe token di qualunque issuer/audience.
  • Algoritmo: HMAC-SHA256 fisso, nessun supporto per alg: none.
  • Segreti: la signing key va da un secret manager (Key Vault, env var, user-secrets), mai hardcoded né in appsettings.json committato.
  • Revoca: i token JWT sono stateless — non sono revocabili prima della scadenza. Tieni TokenExpiration breve (default 1h). Per revoca immediata serve un blocklist store esterno (lato consumer).
  • Lockout per-account: attivo via MaxFailedLoginAttempts + LockoutDuration in LocalIdentityOptions. Configura valori adatti al tuo rischio.
  • Rate-limit IP-level: non incluso di default. Per difesa in profondità contro spraying volumetrico, monta AddRateLimiter/UseRateLimiter di ASP.NET Core sull’endpoint di login.
  • Handler di sviluppo: NoOpAuthenticationHandler e HeaderUserMiddleware sono solo per sviluppo. Verifica che la configurazione di Production usi UseJwtAuthentication.
  • La cache dei permessi (UsePermissionCache) ha TTL: una modifica di ruolo/permesso a runtime diventa effettiva al più tardi dopo il TTL. Per propagazione immediata, riduci il TTL o invalida esplicitamente.
  • Le internal call nella pipeline Actions bypassano il controllo permessi (sono in-process, stesso trust boundary) e non lasciano audit trail: tienine conto nel modello di audit.
  • Visibilità dei dati: usa [OwnedEntity]/[ScopedEntity] invece di filtri manuali — il SG genera i query filter e i bypass via permission.
  • [WithoutFilter]: disabilita i query filter per un tipo. È un privilegio, non un escape hatch: applicalo solo a query in contesti già autorizzati (admin/background), mai per aggirare un filtro inadeguato.
  • Connection string: dal secret manager, mai loggate. Pragmatic non le logga.

LocalDiskFileStorage è inteso per sviluppo/demo. In ogni caso:

  • Path traversal: il parametro container è validato — un valore che risolve fuori da {basePath}/files/ viene rifiutato. Anche le letture (GetAsync/DeleteAsync) restano confinate alla root.
  • Limite dimensione: passa maxFileSizeBytes al costruttore per rifiutare upload oltre soglia (vale anche per stream non seekable). Default 0 = illimitato — imposta un limite in produzione.
  • Content-type / estensione: la validazione del tipo di file caricato è responsabilità del consumer. Non fidarti del content-type dichiarato dal client.
  • In produzione usa un backend dedicato (Azure Blob, S3) servito dietro CDN, non il disco locale dell’host.
  • TLS: con UseSsl = true la connessione rifiuta di autenticarsi se STARTTLS non è disponibile — le credenziali non viaggiano mai in chiaro. Tieni UseSsl = true in produzione.
  • Header injection: i valori degli header (subject, display name, header custom, nome allegato) sono sanificati/codificati — un \r\n non può iniettare header. I nomi di header custom non validi vengono rifiutati.
  • Credenziali SMTP: dal secret manager.
  • Credenziali transport: la connection string RabbitMQ non viene loggata in chiaro (solo host:port/vhost). Tienila comunque in un secret manager.
  • Outbox: la consegna è at-least-once — un consumer può ricevere lo stesso messaggio più volte. Gli handler devono essere idempotenti (es. dedup su MessageId).
  • Deserializzazione: i tipi dei messaggi sono risolti via registry generato dal SG (FQN → Type), non via Type.GetType() su input arbitrario.
  • Log forging: i valori che finiscono nel messaggio di log hanno CR/LF neutralizzati — un input utente non può iniettare righe di log false.
  • Dati sensibili: non loggare password, token, PII. Usa [NotLogged] sui campi sensibili dei tipi che vengono loggati strutturati.
  • CORS: la combinazione Origins: ["*"] + AllowCredentials: true è invalida e viene rifiutata all’avvio con un messaggio chiaro. Per le credenziali, elenca origin espliciti.
  • Termina il TLS al gateway; le rotte interne presuppongono rete fidata.
  • Agent socket (Unix): il socket è 0600 e la sua directory 0700 — il confine di trust è l’uid del processo agent. Non allentare questi permessi.
  • ControlPlane hub: i payload dei comandi sono validati (non vuoti, dimensione limitata, JSON ben formato). L’accesso resta protetto da API key — esponilo solo su rete interna e sempre via HTTPS.

L’endpoint generato POST /_pragmatic/invoke è RPC interno boundary-to-boundary. È fail-closed di default: senza configurazione richiede un principal autenticato. Il client remoto generato propaga l’identità del chiamante inoltrando l’header Authorization sulla chiamata in uscita (handler PragmaticRemoteAuthHandler registrato su ogni named client).

  • Default (raccomandato): nessuna configurazione → l’endpoint richiede autenticazione, l’identità del chiamante viene propagata. Una chiamata senza credenziale viene rifiutata.
  • Policy di servizio: imposta Pragmatic:RemoteBoundaries:InvokeEndpoint:AuthorizationPolicy con il nome di una policy registrata per un controllo service-to-service più stringente.
  • Opt-out rete fidata: Pragmatic:RemoteBoundaries:InvokeEndpoint:AllowAnonymous: true rende l’endpoint anonimo — usalo solo su rete isolata. Non è mai silenziosamente anonimo: l’opt-out è esplicito e tracciabile in configurazione.

⚠️ Se i tuoi host comunicano da contesti senza HTTP context (job/background), non c’è un header da propagare: configura una credenziale di servizio esplicita oppure AllowAnonymous su rete fidata.

  • JWT: signing key ≥256 bit da secret manager; Issuer + Audience configurati; TokenExpiration breve.
  • Autenticazione di Production = JWT (non NoOp/header).
  • Lockout login configurato; rate-limit IP sull’endpoint di login.
  • Storage: limite dimensione upload impostato; backend di produzione (non disco locale).
  • Email: UseSsl = true; credenziali da secret manager.
  • Messaging: handler idempotenti (outbox è at-least-once).
  • Connection string e segreti fuori dai file di configurazione committati.
  • CORS: origin espliciti se servono credenziali.
  • HTTPS ovunque; ControlPlane/Agent solo su rete interna.
  • Remote invoke (/_pragmatic/invoke): lasciato fail-closed, oppure AllowAnonymous: true solo su rete isolata e documentato.
  • Migrations: nessun Force=true di default; backup prima di ogni migrazione distruttiva.