JAAS è la API che, tra gli strumenti forniti all‘interno della Java Platform, è quella che affronta gli aspetti dell‘autenticazione e dell‘autorizzazione. Dopo aver introdotto JAAS e avere trattato, nello scorso articolo, l‘autenticazione, ci apprestiamo a concludere questa serie con l‘esposizione del secondo argomento cruciale di JAAS: l‘autorizzazione.
Introduzione
Con l’articolo di questo mese concludiamo la nostra introduzione teorica alla JAAS-API, affrontando il secondo aspetto di cui si occupa JAAS: l’autorizzazione. Descriveremo come funziona il processo di autorizzazione e successivamente ne esamineremo i principali elementi con un livello di dettaglio superiore a quello tenuto in precedenza.
Il processo di autorizzazione
Il processo di autorizzazione prende piede una volta completata l’autenticazione. È infatti necessario, per poter autorizzare un certo user (utente umano o sistema esterno) a compiere particolari operazioni, che a quest’ultimo sia stato già garantito l’accesso al sistema. Ovvero lo user deve essere stato riconosciuto (attraverso l’esibizione di opportune credential) e gli deve essere stato già concesso l’accesso. Una volta ottenuto l’accesso è necessario vedere quali operazioni è autorizzato a compiere o meno, a partire da una specifica policy.
Il punto centrale che origina richieste di accesso alle risorse è il Subject. Il Subject contiene Principal e Credential associate allo user e viene recuperato dal LoginContext.
I passaggi essenziali in cui si articola il processo sono illustrati di seguito (in un caso in cui venga creata una action) e sintetizzati in figura 1.
Figura 1 – Diagramma di sequenza del processo di autorizzazione (da [2])
Come primo passo, dopo aver eseguito con successo l’autenticazione, abbiamo adesso a disposizione un LoginContext. A questo punto è possibile recuperare il Subject dal LoginContext tramite l’istruzione
Subject sub = lc.getSubject( );
Come secondo passo, una volta recuperato il Subject è possibile eseguire le operazioni (action) utilizzando le credenziali contenute all’interno del Subject. Per fare questo possiamo usare per esempio il metodo static Subject.doAsPrivileged.Questo metodo è usato per eseguire un blocco di codice protetto per conto del Subject.
Il blocco di codice è caratterizzato dal fatto di implementare l’interfaccia java.security.PrivilegedAction o java.security.PrivilegedExceptionAction. Questa interfaccia ha un unico metodo run(), all’interno del quale vanno inserite le istruzioni. Affinche’ queste istruzioni possano essere eseguite il Subject deve essere popolato con i Principal a cui sono stati riconosciuti (nella Policy) gli specifici diritti di accesso alle risorse.
Il compito di eseguire i controlli di accesso alle risorse è svolto da due classi: il SecurityManager e l’AccessController, in stretta relazione e collaborazione reciproca. I diritti d’accesso sono chiamati Permission.
Le Permission (java.security.Permission) sono l’elemento che permette di discriminare quali azioni possono compiere determinate classi cui sono garantiti certi diritti di accesso alle risorse. Queste classi caratterizzano i ProtectionDomain.
Il ProtectionDomain (java.security.ProtectionDomain) permette di raggruppare e isolare tra loro pezzetti di codice in “unità di protezione”.
Un ProtectionDomain racchiude un insieme di classi alle cui istanze è garantito lo stesso insieme di Permission e sono determinati dalla policy correntemente usata. Il dominio di protezione è associato ad una singola grant entry all’interno del file di configurazione della policy.
Per permettere l’esecuzione del codice con un SecurityManager è necessario fornire, oltre ovviamente all’istruzione di “attivazione” dello stesso SecurityManager, anche l’indicazione della posizione del file di policy.
Ecco un esempio di invocazione di programma fatta in un sistema Unix che presenta entrambe queste istruzioni:
java -Djava.security.manager -Djava.security.policy==mypolicy.policy SampleAction
Come si vede l’istruzione -Djava.security.manager serve per eseguire SampleAction usando il SecurityManager mentre l’istruzione -Djava.security.policy permette di selezionare dinamicamente il file di policy.
Il SecurityManager e l’AccessController
Il SecurityManager e l’AccessController hanno la responsabilità di decidere quali richieste di accesso alle risorse di sistema siano da consentire e quali da rifiutare.
Il SecurityManger (java.lang.SecurityManager) è una specie di wrapper che incapsula l’AccessController, ed è tenuto per backward compatibility. L’AccessController (java.security.AccessController), che è l’implementazione di default del SecurityManager, lavora con molte altre classi per ricercare le permission configurate mediante una grant entry in un file di policy.
Per controllare le permission di un object viene usato il metodo checkPermission( ) dell’AccessController.
L’AccessController ha dei metodi doPrivileged( ) che possono essere usati per eseguire specifiche actions come caller privilegiato. Quando viene usato il metodo doPrivileged( ) il metodo checkPermission( ) non controlla le permission delle PrivilegedAction e, di conseguenza, non negherà loro l’accesso alle risorse richieste.
Altra classe significativa è l’AccessControlContext (java.security.AccessControlContext)che viene ricavato usando il metodo getContext( ) dell’AccessController ed è usato per prendere decisioni sull’accesso a risorse in base al contesto in esso incapsulato. L’AccessControlContext è utile nel caso in cui i controlli di sicurezza che avvengono all’interno di un dato contesto necessitino di essere fatti dall’interno di un diverso contesto.
L’AccessControlContext contiene un solo metodo checkPermission, equivalente all’omonimo metodo contenuto nella classe AccessController. Il metodo dell’AccessControlContext, tuttavia, a differenza di quello dell’AccessController consente o blocca gli accessi in base al context che incapsula, e non in base a quello del corrente Thread di esecuzione.
Il Subject
Parliamo nuovamente del Subject (javax.security.auth.Subject) che si conferma essere uno dei punti cardine di JAAS. In questo paragrafo aggiungeremo comunque ulteriori informazioni. Esso costituisce una sorgente di richieste di accesso alle risorse del sistema. Una volta che l’utente è stato autenticato, viene creato un oggetto istanza di classe Subject che viene popolato con le identità ad esso associate. Il Subject è quindi un repository centrale di identità (Principal) e credential.
Dal punto di vista dell’autorizzazione è importante portare l’attenzione su alcuni metodi static contenuti nella classe Subject:
public static Object doAs(final Subject subject, final java.security.PrivilegedAction action);
public static Object doAs(final Subject subject, final java.security.PrivilegedExceptionAction action) throws java.security.PrivilegedActionException;
Entrambi i metodi, prima associano lo specifico Subject all’AccessControlContext del Thread corrente e quindi eseguono la action. Il primo metodo può lanciare runtime exception mentre l’esecuzione normale restituisce un oggetto di classe Object. Il secondo invece si comporta in modo simile tranne per il fatto che riceve come argomento una PrivilegedExceptionAction (java.security.PrivilegedExceptionAction action) anzich� una PrivilegedAction (java.security.PrivilegedAction). Entrambi questi metodi generano l’esecuzione del metodo run( ) della action per conto del Subject.
Abbiamo già accennato in [7] al fatto che JAAS introduce un ulteriore livello di controllo estendendo quelli già presenti nella Java platform. Questi controlli avvengono sulla URL del codice e sull’eventuale firma di chi ha scritto il codice. Questi due elementi costituiscono il CodeSource (java.security.CodeSource). La classe CodeSource è l’insieme di URL e certificati che servono per verificare l’affidabilità di chi ha firmato il codice.
JAAS permette di estendere ulteriormente i controlli aggiungendo il criterio della conoscenza dell’identità di chi richiede l’esecuzione del codice, rappresentata dal Principal.
I metodi seguenti hanno un comportamento simile ai precedenti.
public static Object doAsPrivileged(final Subject subject, final java.security.PrivilegedAction action, final java.security.AccessControlContext acc);
public static Object doAsPrivileged(final Subject subject, final java.security.PrivilegedExceptionAction action, final java.security.AccessControlContext acc) throws java.security.PrivilegedActionException;
La differenza tra queste coppie di metodi sta nel fatto che mentre la prima coppia di metodi associa il Subject all’AccessControlContex del Thread corrente, i secondi usanoquello fornito come argomento. In questo modo le Action possono essere circoscritte ad un AccessControlContext differente da quello attuale.
Le Permission
La classe astratta Permission (java.security.Permission) rappresenta i permessi di accesso (secondo specifiche modalità) a una particolare risorsa del sistema.
Illustreremo alcuni concetti e introdurremo le classi corrispondenti. Gli stessi concetti saranno affrontati successivamente dal punto di vista amministrativo e vedremo a tal punto che questi corrispondono alle dichiarazioni contenute nella policy.
Ogni Permission ha un nome, il cui significato dipende dalla particolare classe concreta, ed è creata tipicamente passando come argomenti una stringa che rappresenta il nome della permission e un secondo parametro che è la “action list”. Il nome può essere il “target”, che corrisponde alla risorsa alla quale disciplinare l’accesso. Ecco un esempio:
perm = new java.io.FilePermission("/tmp/abc", "read");
In questo caso perm è la Permission che rappresenta il diritto di accesso in lettura (la action è infatti “read”) al file “abc” contentuo nella directory “/tmp/”. Al posto di “read” avrebbe potuto esserci una stringa con una serie di action separate da virgole (si chiama infatti “action list”). Nel caso avessimo voluto garantire l’accesso anche in scrittura nella “action list” avremmo usato la stringa “read, write”.
Un metodo importante che deve essere implementato è il metodo implies, che è fondamentale per prendere le giuste decisioni sull’accesso alle risorse. Quando dichiariamo che “permission p1 implies permission p2” intendiamo che se una classe possiede la permission p1 allora questo implica che questa possieda la permission p2.
Per esempio, una istruzione come java.io.FilePermission (“/tmp/*”, “read”) implica l’istruzione java.io.FilePermission(“/tmp/a.txt”, “read”). Ovvero se concedo l’accesso in lettura a tutti i file all’interno della directory “/tmp”, sto concedendo implicitamente l’accesso al file “a.txt” in essa contenuto.
È necessario stare attenti quando si concedono gli accessi alle risorse perch� è possibile concedere diritti molto più estesi di quanto non si supponga.
Per esempio un applet a cui sia stato concesso l’accesso in scrittura a tutto il file system non acquisisce solo il permesso di creare un file, ma anche di alterare risorse del sistema critiche (lo stesso JVM runtime environment).
Esistono poi delle altre classi associate a Permission: PermissionCollection e Permissions.
PermissionCollection (java.security.PermissionCollection) è una collezione omogenea di Permission.
Permissions (java.security.Permissions) è una Collection di istanze di PermissionCollection, e assolve allo scopo di raccogliere insiemi eterogenei di Permission.
In sostanza, l’oggetto Permissions permette di raccogliere le Permission (raccolte omogeneamente tra loro in PermissionCollection) contenute in un ProtectionDomain che corrispondono, dal punto di vista amministrativo, a quelle racchiuse all’interno di un grant statement (entry).
Esiste una classe, in particolare, chiamata BasicPermission (java.security.BasicPermission), sottoclasse di Permission, che può esser estesa nel caso in cui si voglia usare la stessa naming convention. Sue sottoclassi sono, per esempio, java.lang.RuntimePermission, java.security.SecurityPermission, java.util.PropertyPermission, e java.net.NetPermission.
La Policy e i file di Configurazione
Policy (java.security.Policy) è una classe astratta che rappresenta la policy di accesso globale del sistema e di cui sarà necessario fornire una concreta implementazione. In ogni dato momento esiste un unico oggetto Policy in vigore.
Il Policy object correntemente installato può essere ottenuto invocando il metodo getPolicy e può essere settato usando il metodo setPolicy. Esiste poi il metodo refresh che genera il reload della configurazione corrente. Questo meccanismo è dipendente dall’implementazione: è infatti diverso leggere la configurazione da file di configurazione o da database.
Le regole che disciplinano l’accesso alle risorse (ovvero le politiche di sicurezza), sono riportate in un file di configurazione di tipo ASCII (per default) e sono descritte usando un’opportuna sintassi. In ogni caso è possibile intervenire per modificare l’origine delle policy.
Il file di configurazione presenta una serie di grant statement che rispettano la seguente sintassi:
grant [SignedBy "signer_names"] [, CodeBase "URL"] [, Principal [principal_class_name] "principal_name"] [, Principal [principal_class_name] "principal_name"] ... { permission permission_class_name [ "target_name" ] [, "action"] [, SignedBy "signer_names"]; permission ... };
Il “target name” rappresenta le risorse alle quali si vuole disciplinare l’accesso.
“Action” rappresenta il tipo di azione che si vuole concedere alle risorse: possono essere indicate più action, separandole con una virgola. Queste action sono implementate all’interno della particolare classe Permission.
Un esempio concreto di file di policy è il seguente:
grant codeBase "http://www.miosito.it/*", signedBy "MarioRossi" { permission java.io.FilePermission "/temp/*", "read"; permission java.io.SocketPermission "*", "connect"; };
In questo caso abbiamo concesso il diritto di accesso in lettura a tutti i file contenuti nella directory “/temp” e il permesso di attivare una socket connection verso qualunque host (terget list “*”), al codice proveniente dalla URL indicata e firmato con “MarioRossi”.
Per gestire l’intero sistema è necessario provvedere alla configurazione dei suoi componenti, ovvero a settare in opportuni file determinati valori. È possibile modificare l’implementazione della sorgente delle informazioni di gestione della policy utilizzate dal Policy object. Potrebbe essere conservata in un semplice file ASCII così come all’interno di un database.
Il file java.policy è quello che ospita la descrizione della policy di sistema ed è collocato generalmente nelle seguenti directory (si riportano, come da documentazione ufficiale, i tipici percorsi per diversi sistemi operativi).
java.home/lib/security/java.policy (Solaris) java.homelibsecurityjava.policy (Win32)
Il file di policy di sistema è quello che racchiude tutte le dichiarazioni che descrivono i permessi (per esempio consente la lettura di property di sistema non critiche per la sicurezza).
Il file di user policy è collocato generalmente nelle seguenti directory.
user.home/.java.policy (Solaris) user.home.java.policy (Win32)
Con user.home ci si riferisce alla directory home dello user nel sistema.
Nel momento dell’inizializzazione della Policy, il file di sistema viene letto prima e successivamente viene caricato quello utente. Come visto in precedenza, è possibile specificare dinamicamente, mediante un’istruzione data alla linea di comando, quale policy utente usare.
La posizione dei file di policy è a sua volta oggetto di configurazione. È infatti riportata all’interno del file java.security che si trova nelle directory:
java.home/lib/security/java.security (Solaris) java.homelibsecurityjava.security (Win32)
All’interno di questo file troviamo un insieme di istruzioni con una struttura del tipo policy.url.n (dove “n” è un numero intero che individua la posizione di una URL all’interno di una successione che individua una posizione in un elenco ordinato di preferenze). Questa istruzione permette di settare la posizione di file di policy specificandone la URL. La lista viene scandita alla ricerca della prima URL valida.
Ecco un esempio:
policy.url.1=file:C:/policy/.java.policy policy.url.2=file:C:/users/foo/.foo.policy
All’interno del file java.security è possibile configurare la concreta implementazione della policy usando la property policy.provider.
Per default è il valore della property è il seguente:
policy.provider=sun.security.provider.PolicyFile
L’implementazione di default può essere modificata, cambiando il valore della property “policy.provider” settandone il valore con FQN della classe che implementa la policy.
Conclusioni
Con questo articolo si conclude l’esposizione teorica su JAAS. Abbiamo visto come JAAS si inserisca all’interno di una platform già decisamente orientata ad affrontare molte problematiche di sicurezza, fornendo importanti funzionalità. Questa estensione di funzionalità viene inoltre fornita utilizzando soluzioni architetturali (l’uso di provider, configurazioni di politiche di gestione o di componenti in sorgenti esterne indipendenti, la natura pluggable e stacked del framework) che garantiscono l’indipendenza di implementazione e in ultima analisi la flessibilità di utilizzo.
Riferimenti
[1] JavaTM 2 Platform Security Architecture, Sun Microsystem
http://java.sun.com/javase/6/docs/technotes/guides/security/spec/security-spec.doc.html
[2] Rich Helton – Johennie Helton, “Java Security Solutions”, Wiley Publishing, Inc. 2002
[3] Java™ Security Overview, Sun Microsystem
http://java.sun.com/javase/6/docs/technotes/guides/security/overview/jsoverview.html
[4] JavaTM Authentication and Authorization Service (JAAS) – Reference Guide – Sun Microsystem
http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/JAASRefGuide.html
[5] JAAS Authentication Tutorial – Sun Microsystem
http://java.sun.com/j2se/1.4.2/docs/guide/security/jaas/tutorials/GeneralAcnOnly.html
[6] JAAS in Action – Cotè (disponibile per il libero download)
www.jaasbook.com
[7] M. Orrù, “Java Authentication and Authorization Service – I parte: Introduzione alla sicurezza e a JAAS”, Mokabyte 138, Marzo 2009
[8] M. Orrù, “Java Authentication and Authorization Service – II parte: Autenticazione JAAS” – Mokabyte 139, Aprile 2009