Salta ai contenuti

Accesso singolo

L’accesso singolo consente a un provider di identità esterno (IdP) di decidere chi può accedere al tuo server Control Center. Sono supportati due protocolli — SAML 2.0 e OpenID Connect — ed entrambi approdano nello stesso punto: una credenziale del dispositivo appena coniata, indistinguibile da un invito o da un abbinamento tramite QR in ogni passaggio a valle.

Gli accessi SAML sono mediati dal browser. L’IdP non apre mai una connessione verso il tuo server; è il browser dell’utente a trasportare tutto:

browser ──> /saml/login ──> IdP ──POST SAMLResponse──> /saml/acs
│ verify
▼
provision user (maybe)
│
▼
browser handoff: device PSK in the URL
fragment (never a query parameter)

Poiché è il browser a fare da corriere, per gli accessi un server dietro NAT funziona senza problemi: localhost, LAN, Tailscale, VPN o un tunnel vanno tutti bene — solo il browser dell’utente deve raggiungere l’URL ACS. Il relay WebSocket usato dal controllo remoto da telefono non può trasportare il round-trip HTTP, ed è per questo che i punti di ingresso SSO puntano sempre all’origine HTTP del server.

OpenID Connect segue la stessa forma con un round-trip authorization-code + PKCE; i claim vengono letti dall’endpoint del token dell’issuer su TLS, quindi il browser non fornisce mai dati di identità.

Control Center separa le responsabilità in modo deliberato:

Componente Dove Di cosa si occupa
cc_saml nativo packages/cc_natives/native/saml/ Verifica XML-DSig, canonicalizzazione, convalida del profilo SAML — delegate al crate saml in puro Rust fissato, dietro un’ABI C senza stato
SamlService / OidcService cc_server_core stato del login (tracker delle richieste in sospeso, deduplica dei replay), parametri della policy di convalida, la chiamata di provisioning condivisa
SsoProvisioner cc_server_core trova-o-crea l’utente, fissa il subject del provider, concede l’appartenenza secondo la policy di appartenenza automatica
ScimService cc_server_core la superficie push SCIM 2.0 (ciclo di vita di provisioning/deprovisioning)
SsoSettingsService cc_server_core le righe delle connessioni salvate, la riapplicazione a caldo, il token SCIM

Di proposito non esiste alcun fallback crittografico SAML in Dart puro: è proprio nell’XML-DSig e nella canonicalizzazione fatti a mano che vivono le vulnerabilità di signature wrapping, quindi una libreria cc_saml mancante rifiuta sia l’accesso SAML sia l’avvio del server. Il nativo è senza stato per scelta progettuale — lo stato tra richieste vive nei servizi Dart, monouso e di breve durata.

All’interno del nativo verificato e del servizio, in ordine:

  1. la firma dell’asserzione (sempre obbligatoria; quella dell’involucro della risposta è opzionale a seconda della configurazione) — strutturalmente resistente al signature wrapping perché il payload convalidato è vincolato all’elemento verificato,
  2. l’issuer corrisponde all’IdP configurato,
  3. l’audience indica questo service provider,
  4. la destination è il nostro URL ACS,
  5. InResponseTo corrisponde a una AuthnRequest in sospeso che abbiamo emesso (le risposte non richieste, avviate dall’IdP, sono disattivate per impostazione predefinita),
  6. le finestre di validità rientrano nello scostamento dell’orologio (90s predefiniti) e
  7. l’id dell’asserzione non è mai stato visto prima (cache dei replay).

Gli errori tornano come codici tipizzati — signature, expired, audience, destination, issuer, request-match — e gli endpoint rispondono con errori generici, così un attaccante non scopre nulla sul motivo per cui un falso è fallito.

Il primo accesso SSO fissa sull’account il subject immutabile del provider (issuer più il NameID SAML / il sub OIDC, oppure l’externalId SCIM). Da quel momento è il subject — non l’email — a identificare l’accesso. Se in seguito qualcun altro presenta la stessa email dallo stesso IdP o da un altro, l’account con il subject fissato lo rifiuta. È questo che impedisce che un’email riciclata presso un provider si trasformi in un furto di account.

Il provisioning JIT avviene al momento dell’accesso (SsoProvisioner). SCIM è una superficie push separata (ScimService) che fissa lo stesso subject sulla riga dell’utente; non concede l’appartenenza agli spazi di lavoro. Il deprovisioning — SCIM active: false o DELETE — rifiuta se l’account è proprietario di uno spazio di lavoro (prima trasferisci la proprietà). Altrimenti revoca prima ogni credenziale del dispositivo (le sessioni cadono in pochi secondi tramite il watch del dispositivo), rimuove ogni appartenenza agli spazi di lavoro e contrassegna l’account come disattivato. La riga dell’utente sopravvive: l’attribuzione è permanente. La riattivazione ripristina l’accesso ma non riconcede mai le appartenenze.

  • La mappatura gruppo→ruolo avviene all’accesso a partire dagli attributi SAML / dai claim OIDC — i gruppi inviati via SCIM non vengono memorizzati.
  • Il ruolo di proprietario non è raggiungibile dall’SSO — né come ruolo predefinito né tramite una mappatura di gruppo.
  • Il relay WebSocket non è mai un canale SSO; i telefoni che raggiungono il server solo attraverso il relay non possono completare un round-trip del browser verso di esso.