back to top

Autenticazione in MongoDB: gestire utenti, permessi e ruoli

Nelle precedenti lezioni abbiamo visto come eseguire un’istanza di mongod e come collegarci al server locale tramite mongosh. Non abbiamo però dovuto indicare nessun username o password. Il motivo è che MongoDB non presenta alcun utente predefinito e se non si indica esplicitamente non è necessario alcun meccanismo di autenticazione per accedere al server.

Per ragioni di sicurezza è però fondamentale limitare l’accesso ad un database ai soli utenti autorizzati.

Su MongoDB Atlas abbiamo visto come creare un nuovo utente e come definire la lista dei soli indirizzi IP da cui potersi collegare al database.

Vediamo ora come creare degli utenti ed abilitare un meccanismo di autenticazione per un server che eseguiamo in locale. Prima di farlo presentiamo brevemente quali sono i metodi di autenticazione supportati in MongoDB.

Metodi di autenticazione supportati in MongoDB

I metodi di autenticazione attualmente supportati in MongoDB per verificare l’identità di un utente sono:

  • SCRAM Authentication (Salted Challenge Response Authentication Mechanism) è il meccanismo di autenticazione predefinito in MongoDB che consente di autenticare gli utenti tramite nome utente e password.
  • x.509 Certificate Authentication è un meccanismo di autenticazione basato sull’uso di certificati X.509 per verificare l’identità di client e server. Questo metodo può essere anche usato per l’autenticazione fra i server di un Replica Set o fra i membri di un cluster adibiti al partizionamento orizzontale dei dati (Sharded Cluster).
  • Kerberos Authentication è un protocollo di autenticazione per sistemi client/server di grandi dimensioni che consente l’autenticazione attraverso dei token di breve durata detti ticket.
  • LDAP Authentication è un sistema di autenticazione avanzato indicato in ambito aziendale basato sul protocollo LDAP. L’autenticazione di un utente avviene verificando le credenziali fornite tramite connessione ad un server che usa il protocollo LDAP.

Oltre ai metodi per verificare l’identità di un utente, esistono dei meccanismi di autenticazione interna fra i server di un Replica Set per assicurare un certo grado di protezione nella trasmissione dei dati fra i server stessi. Vedremo come creare e configurare un Replica Set nella prossima lezione.

In questa lezione facciamo riferimento solo al sistema di autenticazione SCRAM e per questo vediamo immediatamente come configurare mongod per verificare l’identità degli utenti.

Come abilitare il meccanismo di autenticazione predefinito in MongoDB

Per abilitare il meccanismo di autenticazione predefinito in MongoDB non dobbiamo fare altro che lanciare un’istanza di mongod ed indicare che intendiamo limitare l’accesso ai soli utenti autorizzati. Abbiamo due possibilità: passare delle opzioni direttamente via linea di comando oppure usare un file di configurazione.

Creare un file è la scelta migliore se vogliamo configurare e personalizzare altri aspetti del server e per questo motivo è spesso la scelta consigliata.

I file di configurazione sono scritti in formato YAML e presentano un’estensione .conf su Linux e macOS o .cfg su Windows.

Creiamo allora un file di configurazione e salviamolo in una cartella ricordando il percorso visto che dovremo indicarlo nel momento in cui lanciamo una nuova istanza di mongod.

systemLog:
  destination: file
  path: /path/to/mongo.log
  logAppend: true
storage:
  dbPath: /path/to/mongodb
net:
  bindIp: 127.0.0.1
  port: 27001
processManagement:
  fork: true
security:
  authorization: enabled

Nell’esempio abbiamo indicato che vogliamo usare un file per registrare le operazioni, gli eventi o gli errori che si verificano sul server. Il percorso del file di log mongo.log è stato definito attraverso l’opzione systemLog.path. Assegnando il valore true a systemLog.logAppend mongod andrà ad appendere i nuovi eventi alla fine dello stesso file. È necessario specificare un metodo e il percorso del file di log se si vuole eseguire mongod in background. Prima di eseguire mongod dovremo inoltre assicurarci che esistono tutte le cartelle del percorso in cui vogliamo creare il file di log. In caso contrario riceveremo un messaggio di errore il quale indica che non è stato possibile avviare il processo.

L’opzione storage.dbPath contiene il percorso della cartella in cui devono essere salvati tutti i file del database. Anche in questo caso dobbiamo assicurarci che l’intero percorso di dbPath sia già presente e che siano stati configurati correttamente i permessi di accesso alle cartelle prima di eseguire mongod. Un chiaro segno che il percorso assegnato a storage.dbPath non esiste o che mancano i permessi corretti di accesso alle cartelle si ha se, lanciando mongod, si riceve un messaggio di errore del tipo:

ERROR: child process failed, exited with error number 100

Le opzioni net.bindIp e net.port consentono invece di definire quali devono essere l’indirizzo IP e la porta su cui il server resta in ascolto per ricevere le richieste del client.

Per indicare che vogliamo eseguire mongod in modalità daemon, ovvero come processo in background, abbiamo usato l’opzione processManagement.fork a cui abbiamo assegnato il valore booleano true. Dal momento che il processo sarà eseguito in background, potremo poi controllare il suo stato o interromperlo attraverso gli strumenti forniti dal sistema operativo. Per esempio, su Linux e macOs possiamo usare rispettivamente i comandi ps e kill.

Per abilitare il sistema di accesso usiamo l’opzione security.authorization a cui assegniamo il valore enabled. Così facendo, un utente potrà completare solo le operazioni per le quali ha ottenuto i dovuti permessi.

Una volta creato il file di configurazione, possiamo lanciare una nuova istanza di mongod col comando:

mongod -f path/to/mongod.conf

Dopo aver verificato che il processo è in esecuzione, possiamo collegarci al server col comando:

mongosh --host 127.0.0.1 --port 27001

A questo punto è lecito chiedersi come facciamo a collegarci al server se abbiamo attivato il sistema di autenticazione, ma non esiste ancora alcun utente?

La risposta è che MongoDB attiva quella che viene definita la Localhost Exception. Vuol dire che è possibile abilitare il meccanismo di controllo degli accessi anche prima di aver creato il primo utente. La prima volta dovremo però eseguire l’accesso al computer su cui è in esecuzione mongod senza indicare username e password e dovremo creare il primo utente con privilegi di amministratore nel database admin. Una volta creato il primo utente, la Localhost Exception viene disabilitata e solo gli utenti autorizzati potranno aggiungere nuovi utenti.

Creare un utente con privilegi di amministratore

Sfruttando l’eccezione iniziale, possiamo creare il primo utente con privilegi di amministratore.

Per questo motivo, una volta eseguito l’accesso (mongosh --host 127.0.0.1 --port 27001), selezioniamo il database admin e creiamo il primo utente col comando db.createUser().

Dobbiamo prima selezionare il database admin perché il nuovo utente deve essere creato in questo database.

> use admin

> db.createUser(
  {
    user: "adminUser",
    pwd: passwordPrompt(),
    roles: [
      { role: "userAdminAnyDatabase", db: "admin" },
      { role: "readWriteAnyDatabase", db: "admin" }
    ]
  }
)

Il metodo db.createUser() accetta come primo argomento un documento di opzioni. Nell’esempio definiamo il nome dell’utente attraverso il campo user. La password dell’utente ‘adminUser’ verrà richiesta dopo l’esecuzione del metodo. Così facendo non sarà visibile e non si corre il rischio che possa essere recuperata consultando la cronologia dei comandi.

MongoDB utilizza il sistema dei ruoli per assegnare dei permessi agli utenti. Ad ogni ruolo sono associati dei privilegi. Esistono una serie di ruoli predefiniti, ‘userAdminAnyDatabase’ e ‘readWriteAnyDatabase’ sono due di questi.

All’utente ‘adminUser’ abbiamo assegnato due ruoli. Il primo è userAdminAnyDatabase che consente fra l’altro di creare e modificare gli utenti di qualsiasi database oltre che creare e modificare dei ruoli personalizzati.

userAdminAnyDatabase è uno dei ruoli assegnabili solo ad utenti del database admin.

All’utente ‘adminUser’ abbiamo poi assegnato ‘readWriteAnyDatabase’ che è un altro dei ruoli disponibili per il solo database ‘admin’ e fornisce privilegi di lettura e modifica dei dati su tutte le collezioni dei database eccetto local e config.

Una volta creato l’utente ‘adminUser’, possiamo terminare la sessione col comando exit ed effettuare un nuovo accesso usando le sue credenziali.

> mongosh --host 127.0.0.1 --port 27001 -u adminUser

Eseguendo il comando riportato sopra ci verrà chiesto immediatamente di inserire la password dell’utente ‘userAdmin’ per completare l’accesso.

Una volta collegati al server, possiamo selezionare nuovamente il database ‘admin’ (use admin) e verificare quali sono gli utenti del database con il seguente comando:

db.getUsers()

Otterremo in risposta un solo documento con un campo ‘users’ in cui è presente soltato un documento relativo ad ‘adminUser’.

{
  users: [
    {
      _id: 'admin.adminUser',
      userId: UUID("239376e5-2e2d-417c-8829-3cf3e8b75d26"),
      user: 'adminUser',
      db: 'admin',
      roles: [
        { role: 'userAdminAnyDatabase', db: 'admin' },
        { role: 'readWriteAnyDatabase', db: 'admin' }
      ],
      mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
    }
  ],
  ok: 1
}

Se invece vogliamo ottenere informazioni in merito all’utente corrente, possiamo eseguire il comando:

> db.runCommand({connectionStatus: 1})
{
  authInfo: {
    authenticatedUsers: [ 
      //... 
    ],
    authenticatedUserRoles: [
      // ...
    ]
  },
  ok: 1
}

Come creare degli utenti ed assegnare dei permessi in MongoDB

L’utente ‘adminUser’ ha i permessi per creare nuovi utenti per qualsiasi database. Vediamo allora come creare un nuovo utente ‘ecommerceAdmin‘ a cui assegniamo il ruolo predefinito userAdmin solo per il database ecommerce. Così facendo, l’utente ‘ecommerceAdmin‘ avrà la possibilità di creare e modificare ruoli ed utenti del database ecommerce.

Per creare l’utente ‘ecommerceAdmin’, selezioniamo innanzitutto il database ‘admin’.

> use admin

E creiamo il nuovo utente ‘ecommerceAdmin’ nel database ‘admin’ anche se è un utente con il ruolo di ‘userAdmin’ del database ‘ecommerce’.

Potremmo creare l’utente ‘ecommerceAdmin’ nel database ‘ecommerce’ invece di ‘admin’. In questo caso dovremmo prima assicurarci di cambiare database (use ecommerce). Così facendo, l’utente ‘ecommerceAdmin’ potrebbe però autenticarsi soltanto collegandosi direttamente al database ‘ecommerce’ (mongosh ecommerce --host 127.0.0.1 --port 27001 -u ecommerceAdmin). Al contrario, aggiungendolo al database ‘admin’, l’utente ‘ecommerceAdmin’ potrà collegarsi al server senza specificare il database ‘ecommerce’. I suoi permessi saranno comunque limitati al solo database ‘ecommerce’.

In alternativa, l’utente ‘ecommerceAdmin’ potrebbe collegarsi al server indicando il nome del database da usare per l’autenticazione con l’opzione --authenticationDatabase <nome-database>.

>  db.createUser(
  { 
    user: "ecommerceAdmin", 
    pwd: passwordPrompt(), 
    roles: [ 
      { role: "userAdmin", db: "ecommerce" }
    ] 
  }
)

A questo punto possiamo terminare la sessione e collegarci al server con le credenziali dell’utente ‘ecommerceAdmin’.

> mongosh --host 127.0.0.1 --port 27001 -u ecommerceAdmin

Una volta effettuato l’accesso, se eseguiamo il comando show dbs sarà mostrato solo il nome del database ecommerce.

L’utente ‘ecommerceAdmin’ ha la possibilità di creare degli utenti per il database ‘ecommerce’, per questo motivo selezioniamo subito il database con il comando use ecommerce ed aggiungiamo un nuovo utente ‘ecommerceUser’ a cui assegniamo il ruolo ‘read’ sul database ‘ecommerce’. Questo ruolo fornisce i privilegi di lettura dei dati.

> use ecommerce
> db.createUser(
  { 
    user: "ecommerceUser", 
    pwd: passwordPrompt(), 
    roles: [ 
      { role: "read", db: "ecommerce" }
    ] 
  }
)

Avendo aggiunto l’utente ‘ecommerceUser’ al database ‘ecommerce’, nel momento in cui volesse collegarsi al server, dovrebbe indicare esplicitamente il nome del database ‘ecommerce’.

> mongosh ecommerce --host 127.0.0.1 --port 27001 -u ecommerceUser

Oppure usando l’opzione --authenticationDatabase:

> mongosh --host 127.0.0.1 \
  --port 27001 \
  -u ecommerceUser \
  --authenticationDatabase ecommerce

Tornando a parlare dell’utente ‘ecommerceAdmin’, fra i suoi privilegi esiste la possibilità di revocare o assegnare nuovi ruoli ad un utente esistente. Per farlo basterà usare i metodi db.revokeRolesFromUser() e db.grantRolesToUser(). Per esempio possiamo revocare il ruolo ‘read’ all’utente ‘ecommerUser’:

> db.revokeRolesFromUser(
  "ecommerUser", 
  [{ role: "read", db: "ecommerce"}]
)
{ ok: 1 }

Il metodo db.revokeRolesFromUser riceve un primo argomento obbligatorio che corrisponde al nome dell’utente a cui revocare un ruolo ed un secondo argomento facoltativo che è un array di ruoli da rimuovere.

Al contrario, se vogliamo assegnare il ruolo readWrite sul database ecommerce, possiamo eseguire db.grantRolesToUser() specificando anche in questo caso il nome dell’utente come primo argomento e l’array dei ruoli da aggiungere come secondo argomento. Il ruolo readWrite fornisce tutti i privilegi del ruolo read più la possibilità di modificare i dati.

> db.grantRolesToUser(
  "ecommerceUser", 
  [{ role: "readWrite", db: "ecommerce"}]
)

{ ok: 1 }

Come creare dei ruoli personalizzati

Abbiamo visto solo alcuni dei possibili ruoli predefiniti disponibili in MongoDB, ma ne esistono molti altri con diversi privilegi che sono adatti a molteplici scenari. Sulla documentazione ufficiale è presente una lista dettagliata di tutti i ruoli predefiniti.

Se quelli già presenti non sono sufficienti o non coprono un caso specifico che ci troviamo a risolvere, MongoDB offre comunque la possibilità di creare dei ruoli personalizzati con il metodo db.createRole(). Ovviamente, si tratta di un’operazione per la quale bisogna avere i corretti permessi.

Una delle situazioni in cui può essere utile il metodo db.createRole() è quando i ruoli predefiniti concedono ad un certo utente troppi privilegi rispetto a quelli necessari. Creando dei nuovi ruoli potremo aderire al principio del privilegio minimo (Principle of Least Privilege ). in base al quale gli utenti dovrebbero avere solo i privilegi minimi richiesti per le azioni da compiere.

Vediamo un esempio in cui creiamo un nuovo ruolo che possiede solo i seguenti privilegi sulla collezione ‘orders’ del database ‘ecommerce’:

  • createIndex
  • dropIndex
db.createRole(
  { 
    role: "createDropEcommerceOrdersIndexes", 
    privileges: [ 
      { 
        resource: { db: "ecommerce", collection: "orders" },
        actions: ["createIndex", "dropIndex"] 
      }
    ], 
    roles: [] 
  }
)
{ ok: 1 }

Al metodo db.createRole() passiamo un oggetto di opzioni. Il campo role consente di definire il nome del nuovo ruolo, mentre privileges è un array che contiene tutti i privilegi del nuovo ruolo. Il campo privileges.resource è un documento in cui possiamo limitare l’azione del ruolo creato ad un determinato database e collezione. Lasciando i campi vuoti, consentiamo le azioni su tutte le raccolte di qualsiasi database. Se invece specifichiamo solo il database, le azioni definite sono valide su tutte le collezioni del database.

Il campo actions consente invece di specificare i singoli privilegi da garantire sulle collezioni selezionate.

L’ultimo campo obbligatorio è l’array roles in cui è possibile elencare uno o più ruoli predefiniti i cui privilegi saranno inclusi nel nuovo ruolo che conterrà anche tutti i privilegi indicati nel campo privileges.actions.

Nell’esempio abbiamo creato un nuovo ruolo ‘createDropEcommerceOrdersIndexes’ che ha come privilegi ‘createIndex’ e ‘dropIndex’ ed è valido per la sola collezione ‘orders’ del database ‘ecommerce’.

Nella prossima lezione…

Nella prossima lezione spiegheremo cosa sono i Replica Set, quando è conveniente crearli e come procedere alla loro configurazione.

Pubblicitร