Product SiteDocumentation Site

14.5. Introduzione a SELinux

14.5.1. Princìpi

SELinux (Security Enhanced Linux) è un sistema di controllo degli accessi obbligatorio costruito sull'interfaccia LSM (Linux Security Modules) di Linux. In pratica, il kernel interroga SELinux prima di ogni chiamata di sistema per sapere se il processo è autorizzato ad eseguire una data operazione.
SELinux sfrutta una serie di regole, note comunemente come politiche (policy), per autorizzare o vietare operazioni. Queste regole sono difficili da creare. Fortunatamente vengono fornite due politiche standard (targeted e strict) per evitare il grosso del lavoro di configurazione.
Con SELinux, la gestione dei diritti è completamente diversa dai sistemi Unix tradizionali. I diritti di un processo dipendono dal proprio contesto di sicurezza. Il contesto è definito dall'identità dell'utente che ha avviato il processo, il ruolo e il dominio che l'utente presentava in quel momento. I diritti in realtà dipendono dal dominio, ma le transizioni attraverso i domini sono controllate dai ruoli. Infine, le possibili transizioni tra i ruoli dipendono dall'identità.
Contesti di sicurezza e utenti Unix

Figura 14.3. Contesti di sicurezza e utenti Unix

In pratica, durante l'accesso, all'utente viene assegnato un contesto di sicurezza predefinito (a seconda dei ruoli che è abilitato ad assumere). Questo definisce il dominio corrente, e di conseguenza il dominio che tutti i suoi processi figli acquisiranno. Se si vuole variare il ruolo corrente e il dominio associato, si deve eseguire newrole -r ruolo_r -t dominio_t (di solito esiste un solo dominio permesso per un dato ruolo, per cui il parametro -t si può tralasciare). Questo comando permette l'autenticazione su inserimento della propria password. Questa caratteristica impedisce ai programmi di muoversi automaticamente tra i ruoli. Tali cambiamenti possono avvenire solo se esplicitamente ammessi nella politica di SELinux.
Ovviamente i diritti non si applicano a tutti i soggetti (file, directory, socket, dispositivi, ecc.). Possono variare da oggetto a oggetto. Per applicare i diritti, ad ogni oggetto viene associato un tipo (questo processo è conosciuto come etichettatura). I diritti del dominio allora si esprimono come insiemi di operazioni permesse(vietate) su quei tipi (e, indirettamente, su tutti gli oggetti etichettati con quel tipo).
Per impostazione predefinita, un programma eredita il relativo dominio dall'utente che lo ha eseguito, ma la politica standard di SELinux si aspetta che i programmi più importanti vengano eseguiti in domini dedicati. Per ottenere ciò, questi eseguibili sono etichettati con un tipo univoco (per esempio ssh è etichettato come ssh_exec_t, e quando il programma parte, automaticamente passa al dominio ssh_t). Questo meccanismo automatico di transizione di dominio permette di concedere esclusivamente i diritti richiesti da ciascun programma. Si tratta di un principio fondamentale di SELinux.
Transizioni automatiche attraverso domini

Figura 14.4. Transizioni automatiche attraverso domini

14.5.2. Impostare SELinux

Il supporto di SELinux è incluso nei kernel standard forniti da Debian. Gli strumenti di base in Unix supportano SELinux senza alcuna modifica. Abilitare SELinux quindi è relativamente semplice.
Il comando apt install selinux-basics selinux-policy-default installerà automaticamente i pacchetti richiesti per configurare un sistema SELinux.
Il pacchetto selinux-policy-default fornisce un insieme di regole standard. Per impostazione predefinita, questa politica limita l'accesso ad alcuni servizi fortemente esposti. Le sessioni utente non sono limitate ed è perciò improbabile che SELinux possa bloccare operazioni utente legittime. Comunque, questo aumenta la sicurezza per i servizi in esecuzione sulla macchina. Per installare un insieme di politiche equivalenti alle vecchie regole "restrittive", basta disabilitare il modulo unconfined (la gestione dei moduli è descritta in dettaglio più avanti in questa sezione).
Una volta che la politica è stata installata, bisogna etichettare tutti i file presenti (il che significa assegnare loro un tipo). Questa operazione dev'essere intrapresa manualmente con fixfiles relabel.
Il sistema SELinux a questo punto è pronto. Per abilitarlo, bisogna aggiungere il parametro selinux=1 security=selinux al kernel Linux. Il parametro audit=1 abilita la registrazione dei log di SELinux che memorizzano tutte le operazioni negate/non permesse. Da ultimo, il parametro enforcing=1 mette le regole in applicazione: senza di esso SELinux lavora nella modalità predefinita permissiva dove le azioni bloccate vengono raccolte nei log ma comunque eseguite. Bisogna perciò modificare il file di configurazione del bootloader GRUB per aggiungere i parametri desiderati. Un modo semplice per farlo è quello di modificare la variabile GRUB_CMDLINE_LINUX in /etc/default/grub e di lanciare update-grub. SELinux verrà attivato al riavvio.
Vale la pena notare che lo script selinux-activate automatizza queste operazioni e forza l'etichettatura all'avvio successivo (che evita la creazione di nuovi file non etichettati mentre SELinux non è ancora attivo e mentre l'etichettatura è in corso).

14.5.3. Gestire un sistema SELinux

La politica di SELinux corrisponde ad un insieme modulare di regole, e la loro installazione rileva e abilita i moduli in base ai servizi già presenti. Il sistema è così immediatamente operativo. Comunque, quando un servizio viene installato dopo l'applicazione della politica di SELinux, deve essere possibile abilitare manualmente il modulo corrispondente. Questo è lo scopo del comando semodule. Inoltre, dev'essere possibile definire i ruoli che ogni utente può assumere, che può essere fatto con il comando semanage.
Questi due comandi quindi vengono usati per apportare modifiche all'attuale configurazione di SELinux, che è memorizzata in /etc/selinux/default/. Diversamente da altri file di configurazione che si trovano in /etc/, tutti questi file non devono essere modificati manualmente. Si devono utilizzare i programmi dedicati a questo scopo.

14.5.3.1. Gestione dei moduli SELinux

I moduli disponibili per SELinux sono situati nella directory /usr/share/selinux/default/. Per abilitare uno di questi nella configurazione corrente, si usa semodule -i modulo.pp.bz2. L'estensione pp.bz2 sta per policy package(compressa con bzip2).
Si può rimuovere un modulo dalla configurazione corrente con semodule -r modulo. Infine, il comando semodule -l elenca i moduli che sono attualmente installati. Visualizza anche i loro numeri di versione. I moduli possono essere attivati selettivamente con semodule -e e disabilitai con semodule -d.
# semodule -i /usr/share/selinux/default/abrt.pp.bz2
# semodule -l
abrt    1.5.0   Disabled
accountsd       1.1.0   
acct    1.6.0   
[...]
# semodule -e abrt
# semodule -d accountsd
# semodule -l
abrt    1.5.0
accountsd       1.1.0   Disabled
acct    1.6.0   
[...]
# semodule -r abrt
# semodule -l
accountsd       1.1.0   Disabled
acct    1.6.0   
[...]
semodule carica immediatamente la nuova configurazione tranne nel caso si usi la sua opzione -n. Vale la pena notare che per impostazione predefinita il programma agisce sulla configurazione corrente (riportata nella variabile SELINUXTYPE in /etc/selinux/config), ma si può anche modificarne un'altra specificandola con l'opzione -s.

14.5.3.2. Gestione delle identità

Ogni volta che un utente effettua l'accesso, assume una determinata identità SELinux. Questa identità definisce i ruoli che egli può assumere. Queste due corrispondenze (utente-identità e identità-ruoli) sono configurabili con il comando semanage.
Bisogna assolutamente leggere la pagina di manuale semanage(8), anche se la sintassi del comando sembra essere simile per tutti i concetti che vengono gestiti. Si troveranno opzioni comuni a tutti i sotto-comandi: -a per aggiungere, -d per eliminare, -m per modificare, -l per elencare, e -t per indicare un tipo (o un dominio).
semanage login -l elenca la corrispondenza in uso degli identificatori degli utenti con le identità SELinux. Gli utenti che non hanno un riferimento esplicito acquisiscono l'identità riportata nella voce __default__. Il comando semanage login -a -s user_u utente associa l'identità user_u al dato utente. Infine, semanage login -d utente rimuove la voce corrispondente assegnata all'utente.
# semanage login -a -s user_u rhertzog
# semanage login -l

Login Name           SELinux User         MLS/MCS Range        Service

__default__          unconfined_u         SystemLow-SystemHigh *
rhertzog             user_u               SystemLow            *
root                 unconfined_u         SystemLow-SystemHigh *
system_u             system_u             SystemLow-SystemHigh *
# semanage login -d rhertzog
semanage user -l elenca la corrispondenza delle identità degli utenti in SELinux con i ruoli assegnati. L'aggiunta di una nuova identità richiede la definizione sia dei ruoli corrispondenti sia di un prefisso di etichetta utilizzato per assegnare un tipo ai file personali (/home/utente/*). Il prefisso deve essere preso da user, staff, e sysadm. Il prefisso «staff» ha come risultato file di tipo «staff_home_dir_t». Per creare una nuova identità per l'utente in SELinux basta lanciare semanage user -a -R ruoli -P prefisso identità. Infine, è possibile rimuovere un'identità di SELinux con semanage user -d identity.
# semanage user -a -R 'staff_r user_r' -P staff test_u
# semanage user -l

                Labeling   MLS/       MLS/                          
SELinux User    Prefix     MCS Level  MCS Range             SELinux Roles

root            sysadm     SystemLow  SystemLow-SystemHigh  staff_r sysadm_r system_r
staff_u         staff      SystemLow  SystemLow-SystemHigh  staff_r sysadm_r
sysadm_u        sysadm     SystemLow  SystemLow-SystemHigh  sysadm_r
system_u        user       SystemLow  SystemLow-SystemHigh  system_r
test_u          staff      SystemLow  SystemLow             staff_r user_r
unconfined_u    unconfined SystemLow  SystemLow-SystemHigh  system_r unconfined_r
user_u          user       SystemLow  SystemLow             user_r
# semanage user -d test_u

14.5.3.3. Gestire i contesti dei file, le porte e i booleani

Ogni modulo SELinux fornisce un insieme di regole per l'etichettatura dei file, ma è anche possibile aggiungerne di personalizzate per far fronte a casi specifici. Per esempio, se si vuole che il server web possa leggere i file dentro la gerarchia /srv/www/, si deve lanciare semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?" seguito da restorecon -R /srv/www/. Il primo comando registra le nuove regole sull'etichettatura e il secondo reimposta i tipi di file in base alle regole di etichettatura correnti.
In modo del tutto simile, le porte TCP/UDP sono etichettate in modo da assicurare che solo il rispettivo demone possa rimanere in ascolto su di esse. Per esempio, se si vuole che il server web rimanga in ascolto su porta 8080, è consigliabile seguire semanage port -m -t http_port_t -p tcp 8080.
Alcuni moduli SELinux esportano opzioni booleane che si possono personalizzare per variare il comportamento delle regole predefinite. L'utilità getsebool viene usata per ispezionare tali opzioni (getsebool booleano mostra un'opzione, e getsebool -a le mostra tutte). Il comando setsebool booleano valore modifica il valore corrente di un'opzione booleana. L'opzione -P rende permanente la modifica, cioè il nuovo valore diventa il predefinito e questo rimarrà tale nei successivi riavvii. L'esempio sotto concede ai web server l'accesso alle directory home (utile quando gli utenti hanno siti web personali in ~/public_html/).
# getsebool httpd_enable_homedirs
httpd_enable_homedirs --> off
# setsebool -P httpd_enable_homedirs on
# getsebool httpd_enable_homedirs 
httpd_enable_homedirs --> on

14.5.4. Adattare le regole

Dato che la politica di SELinux è modulare, potrebbe essere interessante sviluppare nuovi moduli per le applicazioni (eventualmente personalizzate) che ne sono prive. Questi nuovi moduli quindi completerebbero la politica di riferimento.
Per creare nuovi moduli, è richiesto il pacchetto selinux-policy-dev oltre a selinux-policy-doc. Quest'ultimo contiene la documentazione delle regole standard (/usr/share/doc/selinux-policy-doc/html/) e file di esempio che possono essere usati come modelli per creare nuovi moduli. Installiamo questi file ed esaminiamoli più da vicino:
$ cp /usr/share/doc/selinux-policy-doc/Makefile.example Makefile
$ cp /usr/share/doc/selinux-policy-doc/example.fc ./
$ cp /usr/share/doc/selinux-policy-doc/example.if ./
$ cp /usr/share/doc/selinux-policy-doc/example.te ./
Il file .te è il più importante. Definisce le regole. Il file .fc definisce i "contesti dei file", che sono i tipi assegnati ai file relativi a questo modulo. I dati all'interno del file .fc sono usati durante la fase di etichettatura dei file. Infine, il file .if definisce l'interfaccia del modulo: si tratta di una serie di "funzioni pubbliche", che altri moduli possono utilizzare per interagire correttamente con il modulo che si sta creando.

14.5.4.1. Scrivere un file .fc

Analizzare l'esempio sotto dovrebbe essere sufficiente per capire la struttura di un file di questo tipo. Si possono usare espressioni regolari per assegnare lo stesso contesto di sicurezza a file multipli, oppure anche a un intero albero di directory.

Esempio 14.2. File example.fc

# l'eseguibile miaapp avrà:
# label: system_u:object_r:miaapp_exec_t
# sensibilità MLS: s0
# categorie MCS : <nessuna>

/usr/sbin/miaapp         --      gen_context(system_u:object_r:miaapp_exec_t,s0)

14.5.4.2. Scrivere un file .if benutze

Nell'esempio sotto, la prima interfaccia («miaapp_domtrans») controlla chi può eseguire l'applicazione. La seconda («miaapp_lettura_log») concede i diritti di lettura sui file di log dell'applicazione.
Ogni interfaccia deve generare un insieme valido di regole che può essere incluso in un file .te. Si deve perciò dichiarare tutti i tipi che si usano (con la macro gen_require), e usare direttive standard per concedere i diritti. Da notare, comunque, che si possono utilizzare le interfacce fornite dagli altri moduli. Nella prossima sezione si approfondirà maggiormente come esprimere questi diritti.

Esempio 14.3. File example.if

## <summary>Myapp example policy</summary>
## <desc>
##      <p>
##              More descriptive text about myapp.  The <desc>
##              tag can also use <p>, <ul>, and <ol>
##              html tags for formatting.
##      </p>
##      <p>
##              This policy supports the following myapp features:
##              <ul>
##              <li>Feature A</li>
##              <li>Feature B</li>
##              <li>Feature C</li>
##              </ul>
##      </p>
## </desc>
#

########################################
## <summary>
##      Execute a domain transition to run myapp.
## </summary>
## <param name="domain">
##      Domain allowed to transition.
## </param>
#
interface(`myapp_domtrans',`
        gen_require(`
                type myapp_t, myapp_exec_t;
        ')

        domtrans_pattern($1,myapp_exec_t,myapp_t)
')

########################################
## <summary>
##      Read myapp log files.
## </summary>
## <param name="domain">
##      Domain allowed to read the log files.
## </param>
#
interface(`myapp_read_log',`
        gen_require(`
                type myapp_log_t;
        ')

        logging_search_logs($1)
        allow $1 myapp_log_t:file r_file_perms;
')

14.5.4.3. Scrivere un file .te

Osservare il file example.te:
policy_module(myapp,1.0.0) 1

########################################
#
# Declarations
#

type myapp_t; 2
type myapp_exec_t;
domain_type(myapp_t)
domain_entry_file(myapp_t, myapp_exec_t) 3

type myapp_log_t;
logging_log_file(myapp_log_t) 4

type myapp_tmp_t;
files_tmp_file(myapp_tmp_t)

########################################
#
# Myapp local policy
#

allow myapp_t myapp_log_t:file { read_file_perms append_file_perms }; 5

allow myapp_t myapp_tmp_t:file manage_file_perms;
files_tmp_filetrans(myapp_t,myapp_tmp_t,file)

1

Il modulo dev'essere identificato da nome e numero di versione. Questa direttiva è obbligatoria.

2

Se il modulo introduce nuovi tipi, deve dichiararli con direttive come questa. Non bisogna esitare a creare tanti tipi quanti necessari piuttosto che concedere troppi inutili diritti.

3

Queste interfacce definiscono il tipo miaapp_t come un dominio di processo che deve essere usato da ogni eseguibile etichettato con miaapp_exec_t. Implicitamente, ciò aggiunge l'attributo exec_type a tutti questi soggetti, che a loro volta permettono ad altri moduli di concedere i diritti di esecuzione su questi programmi: per esempio, il modulo userdomain concede ai processi con dominio user_t, staff_t e sysadm_t di eseguirli. I domini di altre applicazioni circoscritte non avranno i diritti di eseguirli, finché le regole non concedono loro diritti simili (è questo il caso, per esempio, di dpkg con il relativo dominio dpkg_t).

4

logging_log_file è un'interfaccia fornita dalla politica di riferimento. Essa indica che i file etichettati con quel dato tipo sono file di log che possono beneficiare delle regole associate (per esempio concedendo i diritti a logrotate in modo che possa manipolarli).

5

La direttiva allow è la direttiva base per autorizzare un'operazione. Il primo parametro è il dominio del processo a cui è concessa l'esecuzione dell'operazione. Il secondo definisce l'oggetto che un processo del primo dominio può manipolare. Questo parametro si definisce come «tipo:classe» dove tipo è il proprio tipo SELinux e classe descrive la natura dell'oggetto (file, directory, socket, fifo, ecc.). Infine, l'ultimo parametro descrive i permessi (le operazioni consentite).
I permessi sono definiti come un insieme di operazioni consentite e seguono questo modello: { operazione1 operazione2 }. Si possono usare comunque anche macro che rappresentano i permessi più comuni. L'elenco si trova in /usr/share/selinux/devel/include/support/obj_perm_sets.spt.
La seguente pagina web fornisce una lista relativamente esaustiva delle classi di soggetti, e i permessi che possono essere consentiti.
Ora si deve trovare l'insieme minimo di regole necessarie per assicurare che l'applicazione o il servizio in questione funzioni correttamente. Per ottenere ciò, bisogna avere una buona conoscenza di come funziona l'applicazione e di che genere di dati vengono gestiti e/o prodotti.
È comunque possibile un approccio empirico. Una volta che i soggetti rilevanti sono stati correttamente etichettati, si può usare l'applicazione in modalità permissiva: che verrebbero bloccate vengono registrate ma vengono comunque eseguite. Analizzando i log, si possono identificare le operazioni da consentire. Questo è un esempio di una di queste voci di log:
avc:  denied  { read write } for  pid=1876 comm="syslogd" name="xconsole" dev=tmpfs ino=5510 scontext=system_u:system_r:syslogd_t:s0 tcontext=system_u:object_r:device_t:s0 tclass=fifo_file permissive=1
Per comprendere meglio questo messaggio, studiamolo pezzo per pezzo.

Tabella 14.1. Analisi di un tracciamento di SELinux

MessaggioDescrizione
avc: deniedUn'operazione è stata negata.
{ read write }Questa operazione ha richiesto i permessi di lettura e scrittura.
pid=1876Il processo con PID 1876 ha eseguito l'operazione (o ha tentato di eseguirla).
comm="syslogd"Il processo era un'istanza del programma syslogd.
name="xconsole"L'oggetto di destinazione è stato chiamato xconsole. A volte invece si può avere - con il percorso completo - anche un "percorso" variabile.
dev=tmpfsIl device che contiene l'oggetto di destinazione è un tmpfs (un file system in memoria). Per un normale disco, si vede proprio la partizione (per esempio: "sda3").
ino=5510L'oggetto è identificato dall'inode numero 5510.
scontext=system_u:system_r:syslogd_t:s0Questo è il contesto di sicurezza del processo che ha eseguito l'operazione.
tcontext=system_u:object_r:device_t:s0Questo è il contesto di sicurezza dell'oggetto di destinazione.
tclass=fifo_fileL'oggetto di destinazione è un file FIFO.
Dall'osservazione di questa voce di log, è possibilie costruire una regola che può permettere questa operazione. Per esempio: allow syslogd_t device_t:fifo_file { read write }. Questo processo può essere automatizzato, ed è esattamente ciò che offre il comando audit2allow (del pacchetto policycoreutils). Questo approccio è utile solo se i vari soggetti sono già etichettati correttamente secondo ciò che dev'essere ristretto. In ogni caso, bisognerà rivedere attentamente le regole generate e validarle a seconda della propria conoscenza dell'applicazione. In effetti, questo approccio tende a concedere più diritti di quelli realmente necessari. La soluzione corretta è spesso quella di creare nuovi tipi e di concedere i diritti solo a quei tipi. Può anche accadere che negare un'operazione non sia fatale per l'applicazione, nel qual caso sarebbe meglio aggiungere una regola «dontaudit» per evitare la voce di log nonostante l'effettivo diniego.

14.5.4.4. Compilare i file

Una volta che i 3 file (example.if, example.fc e example.te) corrispondono alle proprie aspettative per le nuove regole, basta lanciare make NAME=devel per generare un modulo nel file example.pp (può essere immediatamente caricato con semodule -i example.pp). Se sono definiti diversi moduli, make verranno creati tutti i rispettivi file .pp.