Product SiteDocumentation Site

14.5. Introduction à SELinux

14.5.1. Les principes

SELinux (Security Enhanced Linux) est un système de contrôle d'accès obligatoire (Mandatory Access Control) qui s'appuie sur l'interface Linux Security Modules fournie par le noyau Linux. Concrètement, le noyau interroge SELinux avant chaque appel système pour savoir si le processus est autorisé à effectuer l'opération concernée.
SELinux s'appuie sur un ensemble de règles (policy) pour autoriser ou interdire une opération. Ces règles sont assez délicates à créer, mais heureusement deux jeux de règles standards (targeted et strict) sont fournies pour éviter le plus gros du travail de configuration.
Le système de permissions de SELinux est totalement différent de ce qu'offre un système Unix traditionnel. Les droits d'un processus dépendent de son contexte de sécurité. Le contexte est défini par l'identité de celui qui a démarré le processus, le rôle et le domaine qu'il avait à ce moment. Les permissions proprement dites dépendent du domaine, mais les transitions entre les domaines sont contrôlées par les rôles. Enfin, les transitions autorisées entre rôles dépendent de l'identité.
Contextes de sécurité et utilisateurs Unix

Figure 14.1. Contextes de sécurité et utilisateurs Unix

In practice, during login, the user gets assigned a default security context (depending on the roles that they should be able to endorse). This defines the current domain, and thus the domain that all new child processes will carry. If you want to change the current role and its associated domain, you must call newrole -r role_r -t domain_t (there is usually only a single domain allowed for a given role, the -t parameter can thus often be left out). This command authenticates you by asking you to type your password. This feature forbids programs to automatically switch roles. Such changes can only happen if they are explicitly allowed in the SELinux policy.
Bien entendu, les droits ne s'appliquent pas universellement à tous les objets (fichiers, répertoires, sockets, périphériques, etc.), ils peuvent varier d'un objet à l'autre. Pour cela, chaque objet est associé à un type (on parle d'étiquetage). Les droits des domaines s'expriment donc en termes d'opérations autorisées (ou non) sur ces types (donc implicitement sur tous les objets qui sont marqués avec le type correspondant).
Par défaut, un programme exécuté hérite du domaine de l'utilisateur qui l'a démarré. Mais pour la plupart des programmes importants, les règles SELinux standards prévoient de les faire fonctionner dans un domaine dédié. Pour cela, ces exécutables sont étiquetés avec un type dédié (par exemple ssh est étiqueté avec ssh_exec_t et lorsque le programme est démarré, il bascule automatiquement dans le domaine ssh_t). Ce mécanisme de changement automatique de domaine permet de ne donner que les droits nécessaires au bon fonctionnement de chaque programme et est à la base de SELinux.
Transitions automatiques entre domaines

Figure 14.2. Transitions automatiques entre domaines

14.5.2. La mise en route

Le code de SELinux est intégré dans les noyaux standards fournis par Debian et les programmes Unix de base le gèrent sans modification. Il est donc relativement simple d'activer SELinux.
La commande apt install selinux-basics selinux-policy-default installera automatiquement les paquets nécessaires pour configurer un système SELinux.
Le paquet selinux-policy-default contient un ensemble de règles standards. Par défaut, l'ensemble de règles ne restreint les accès que pour certains services très exposés. Les sessions utilisateur ne sont pas restreintes et il n'y a donc que peu de risques que SELinux bloque des opérations légitimes des utilisateurs. En revanche, cela permet d'apporter un surcroît de sécurité pour les services système fonctionnant sur la machine. Pour obtenir l'équivalent des anciennes règles « strictes », il faut simplement désactiver le module unconfined (la gestion des modules est détaillée plus loin).
Une fois les règles installées, il reste à étiqueter tous les fichiers disponibles (il s'agit de leur affecter un type). C'est une opération qu'il faut déclencher manuellement avec fixfiles relabel.
Le système SELinux est prêt, il ne reste plus qu'à l'activer. Pour cela, il faut passer le paramètre selinux=1 security=selinux au noyau Linux. Le paramètre audit=1 active les traces SELinux qui enregistrent les différentes opérations qui ont été refusées. Enfin, le paramètre enforcing=1 permet de mettre en application l'ensemble des règles : en effet, par défaut SELinux fonctionne en mode permissive où les actions interdites sont tracées mais malgré tout autorisées. Il faut donc modifier le fichier de configuration du chargeur de démarrage GRUB pour ajouter les paramètres désirés. Le plus simple pour cela est de modifier la variable GRUB_CMDLINE_LINUX dans /etc/default/grub et d'exécuter update-grub. Au démarrage suivant, SELinux sera actif.
Signalons que le script selinux-activate automatise ces opérations et permet de forcer un étiquetage au prochain redémarrage, ce qui évite d'avoir des fichiers non étiquetés créés alors que SELinux n'était pas encore actif et que l'étiquetage était en cours.

14.5.3. La gestion d'un système SELinux

L'ensemble de règles SELinux est modulaire et son installation détecte et active automatiquement tous les modules pertinents en fonction des services déjà installés. Ainsi, le système est immédiatement fonctionnel. Toutefois, lorsqu'un service est installé après les règles SELinux, il faut pouvoir activer manuellement un module de règles. C'est le rôle de la commande semodule. En outre, il faut pouvoir définir les rôles accessibles à chaque utilisateur ; pour cela c'est la commande semanage qu'il faudra utiliser.
Ces deux commandes modifient donc la configuration SELinux courante qui est stockée dans /etc/selinux/default/. Contrairement à ce qui se pratique d'habitude avec les fichiers de configuration de /etc/, ces fichiers ne doivent pas être modifiés manuellement. Il faut les manipuler en utilisant les programmes prévus pour cela.

14.5.3.1. Gestion des modules SELinux

Les modules SELinux disponibles sont stockés dans le répertoire /usr/share/selinux/default/. Pour activer un de ces modules dans la configuration courante, il faut employer semodule -i module.pp.bz2. L'extension pp.bz2 signifie policy package que l'on pourrait traduire par « paquet de règles » (comprimé avec bzip2).
À l'inverse, la commande semodule -r module retire un module de la configuration courante. Enfin, la commande semodule -l liste les modules qui sont actuellement installés. La commande inclut également le numéro de version du module.
# semodule -i /usr/share/selinux/default/abrt.pp.bz2
libsemanage.semanage_direct_install_info: abrt module will be disabled after install as there is a disabled instance of this module present in the system.
# semodule -l
accountsd
acct
[...]
# semodule -e abrt
# semodule -d accountsd
# semodule -l
abrt
acct
[...]
# semodule -r abrt
libsemanage.semanage_direct_remove_key: abrt module at priority 100 is now active.semodule -l
semodule recharge immédiatement la nouvelle configuration, sauf si l'on utilise l'option -n. Signalons également que le programme modifie par défaut la configuration courante (celle indiquée par la variable SELINUXTYPE dans /etc/selinux/config) mais qu'on peut en modifier une autre grâce à l'option -s.

14.5.3.2. Gestion des identités

Chaque fois qu'un utilisateur se connecte, il se voit attribuer une identité SELinux, qui va définir les rôles qu'il va pouvoir assumer. Ces deux correspondances (de l'utilisateur vers l'identité SELinux et de cette identité vers les rôles) se configurent grâce à la commande semanage.
You should definitely read the semanage(8) manual page. All the managed concepts have their own manual page; for instance, semanage-login(8). Even if the command's syntax tends to be similar for all the concepts which are managed, it is recommended to read its manual page. You will find common options to most sub-commands: -a to add, -d to delete, -m to modify, -l to list, and -t to indicate a type (or domain).
semanage login -l liste les correspondances existantes entre identifiants d'utilisateurs et identités SELinux. Si un utilisateur n'a pas de correspondance explicite, il aura l'identité indiquée en face de __default__. La commande semanage login -a -s user_u utilisateur va associer l'identité user_u à l'utilisateur. Enfin, semanage login -d utilisateur va retirer la correspondance affectée à l'utilisateur.
# semanage login -a -s user_u rhertzog
# semanage login -l

Login Name           SELinux User         MLS/MCS Range        Service

__default__          unconfined_u         s0-s0:c0.c1023       *
rhertzog             user_u               s0                   *
root                 unconfined_u         s0-s0:c0.c1023       *
# semanage login -d rhertzog
semanage user -l liste les correspondances entre identité SELinux et rôles possibles. Ajouter une nouvelle identité nécessite de préciser d'une part les rôles correspondants et d'autre part, un préfixe d'étiquetage qui définira le type affecté aux fichiers personnels (/home/utilisateur/*). Le préfixe est à choisir entre user, staff et sysadm. Un préfixe « staff » donnera des fichiers typés staff_home_dir_t. La commande créant une identité est semanage user -a -R rôles -P préfixe identité. Enfin, une identité peut être supprimée avec semanage user -d identité.
# 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     s0         s0-s0:c0.c1023                 staff_r sysadm_r system_r
staff_u         staff      s0         s0-s0:c0.c1023                 staff_r sysadm_r
sysadm_u        sysadm     s0         s0-s0:c0.c1023                 sysadm_r
system_u        user       s0         s0-s0:c0.c1023                 system_r
test_u          staff      s0         s0                             staff_r user_r
unconfined_u    unconfined s0         s0-s0:c0.c1023                 system_r unconfined_r
user_u          user       s0         s0                             user_r
# semanage user -d test_u

14.5.3.3. Gestion des contextes de fichiers, des ports et des booléens

Chaque module SELinux fournit un ensemble de règles d'étiquetage des fichiers, mais il est également possible de rajouter des règles d'étiquetage spécifiques afin de les adapter à un cas particulier. Ainsi, pour rendre toute l'arborescence /srv/www/ accessible au serveur web, on pourrait exécuter semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?", puis restorecon -R /srv/www/. La première commande enregistre la nouvelle règle d'étiquetage et la seconde restaure les bonnes étiquettes en fonction des règles enregistrées.
D'une manière similaire, les ports TCP/UDP sont étiquetés afin que seuls les démons correspondants puissent y écouter. Ainsi, si l'on veut que le serveur web puisse également écouter sur le port 8 080, il faut exécuter la commande semanage port -m -t http_port_t -p tcp 8080.
Les modules SELinux exportent parfois des options booléennes qui influencent le comportement des règles. L'utilitaire getsebool permet de consulter l'état de ces options (getsebool booléen affiche une option et getsebool -a les affiche toutes). La commande setsebool booléen valeur change la valeur courante d'une option. L'option -P rend le changement permanent, autrement dit la nouvelle valeur sera celle par défaut et sera conservée au prochain redémarrage. L'exemple ci-dessous permet au serveur web d'accéder aux répertoires personnels des utilisateurs (utile dans le cas où ils ont des sites web personnels dans ~/public_html/ par exemple).
# 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. L'adaptation des règles

Puisque l'ensemble des règles (que l'on nomme policy) est modulaire, il peut être intéressant de développer de nouveaux modules pour les applications (éventuellement spécifiques) qui n'en disposent pas encore, ces nouveaux modules venant alors compléter la reference policy.
Le paquet selinux-policy-dev sera nécessaire, ainsi que selinux-policy-doc. Ce dernier contient la documentation des règles standards (/usr/share/doc/selinux-policy-doc/html/) et des fichiers exemples permettant de créer de nouveaux modules. Installons ces fichiers pour les étudier de plus près :
$ 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 ./
Le fichier .te est le plus important : il définit les règles à proprement parler. Le fichier .fc définit les « contextes des fichiers », autrement dit les types affectés aux fichiers relatifs à ce module. Les informations du .fc sont utilisées lors de l'étiquetage des fichiers sur le disque. Enfin, le fichier .if définit l'interface du module ; il s'agit d'un ensemble de « fonctions publiques » qui permettent à d'autres modules de s'interfacer proprement avec celui en cours de création.

14.5.4.1. Rédiger un fichier .fc

La lecture de l'exemple qui suit suffit à comprendre la structure d'un tel fichier. Il est possible d'employer une expression rationnelle pour affecter le même contexte à plusieurs fichiers, voire à toute une arborescence.

Exemple 14.2. Fichier example.fc

# myapp executable will have:
# label: system_u:object_r:myapp_exec_t
# MLS sensitivity: s0
# MCS categories: <none>

/usr/sbin/myapp         --      gen_context(system_u:object_r:myapp_exec_t,s0)

14.5.4.2. Rédiger un fichier .if

Dans l'exemple suivant, la première interface (myapp_domtrans) sert à contrôler qui a le droit d'exécuter l'application et la seconde (myapp_read_log) fournit un droit de lecture sur les fichiers de logs de l'application.
Chaque interface doit générer un ensemble correct de règles comme s'il était directement placé dans un fichier .te. Il faut donc déclarer tous les types employés (avec la macro gen_require) et employer les directives standards pour attribuer des droits. Notons toutefois qu'il est possible d'employer des interfaces fournies par d'autres modules. La prochaine section en dévoilera plus sur la manière d'exprimer ces droits.

Exemple 14.3. Fichier 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. Rédiger un fichier .te

Analysons le contenu du fichier 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

Le module doit être identifié par son nom et par son numéro de version. Cette directive est requise.

2

Si le module introduit de nouveaux types, il doit les déclarer avec des directives comme celle-ci. Il ne faut pas hésiter à créer autant de types que nécessaires, plutôt que distribuer trop de droits inutiles.

3

Ces interfaces précisent que le type myapp_t est prévu pour être un domaine de processus et qu'il doit être employé pour tout exécutable étiqueté par myapp_exec_t. Implicitement, cela ajoute un attribut exec_type sur ces objets. Sa présence permet à d'autres modules de donner le droit d'exécuter ces programmes : ainsi, le module userdomain va permettre aux processus de domaine user_t, staff_t et sysadm_t de les exécuter. Les domaines d'autres applications confinées n'auront pas le droit de l'exécuter, sauf si les règles prévoient des droits similaires (c'est le cas par exemple pour dpkg avec le domaine dpkg_t).

4

logging_log_file is an interface provided by the reference policy. It indicates that files labeled with the given type are log files which ought to benefit from the associated rules (for example, granting rights to logrotate so that it can manipulate them).

5

La directive allow est la directive de base qui permet d'autoriser une opération. Le premier paramètre est le domaine du processus qui sera autorisé à effectuer l'opération. Le second décrit l'objet qu'un processus du domaine aura le droit de manipuler. Ce paramètre prend la forme « type:genre » où type est son type SELinux et où genre décrit la nature de l'objet (fichier, répertoire, socket, fifo, etc.). Enfin, le dernier paramètre décrit les permissions (les opérations qui sont autorisées).
Les permissions se définissent comme des ensembles d'opérations autorisées et prennent la forme { operation1 operation2 }. Il est également possible d'employer des macros qui correspondent aux ensembles de permissions les plus utiles. Le fichier /usr/share/selinux/devel/include/support/obj_perm_sets.spt permet de les découvrir.
The following web page provides a relatively exhaustive list of object classes, and permissions that can be granted.
Il ne reste plus qu'à trouver l'ensemble minimal des règles nécessaires au bon fonctionnement du service ou de l'application ciblé(e) par le module. Pour cela, il est préférable de bien connaître le fonctionnement de l'application et d'avoir une idée claire des flux de données qu'elle gère et/ou génère.
Toutefois, une approche empirique est possible. Une fois les différents objets impliqués correctement étiquetés, on peut utiliser l'application en mode permissif : les opérations normalement interdites sont tracées mais réussissent tout de même. Il suffit alors d'analyser ces traces pour identifier les opérations qu'il faut autoriser. Voici à quoi peut ressembler une de ces traces :
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
Pour mieux comprendre ce message, analysons-le bout par bout.

Tableau 14.1. Analyse d'une trace SELinux

MessageDescription
avc: deniedUne opération a été refusée.
{ read write }Cette opération requérait les permissions read et write.
pid=1876Le processus ayant le PID 1876 a exécuté l'opération (ou essayé de l'exécuter).
comm="syslogd"Le processus était une instance de la commande syslogd.
name="xconsole"L'objet visé s'appelait xconsole. Dans certains cas on peut aussi avoir une variable « path », avec un chemin d'accès complet.
dev=tmpfsThe device hosting the target object is a tmpfs (an in-memory filesystem). For a real disk, you could see the partition hosting the object (for example, “sda3”).
ino=5510L'objet est identifié par le numéro d'inode 5510.
scontext=system_u:system_r:syslogd_t:s0C'est le contexte de sécurité courant du processus qui a exécuté l'opération.
tcontext=system_u:object_r:device_t:s0C'est le contexte de sécurité de l'objet cible.
tclass=fifo_fileL'objet cible est un fichier FIFO.
By observing this log entry, it is possible to build a rule that would allow this operation. For example, allow syslogd_t device_t:fifo_file { read write }. This process can be automated, and it is exactly what the audit2allow command (of the policycoreutils package) offers. This approach is only useful if the various objects are already correctly labeled according to what must be confined. In any case, you will have to carefully review the generated rules and validate them according to your knowledge of the application. Effectively, this approach tends to grant more rights than are really required. The proper solution is often to create new types and to grant rights on those types only. It also happens that a denied operation isn't fatal to the application, in which case it might be better to just add a “dontaudit” rule to avoid the log entry despite the effective denial.

14.5.4.4. Compilation des fichiers

Once the 3 files (example.if, example.fc, and example.te) match your expectations for the new rules, rename them to myapp.extension and run make NAME=devel to generate a module in the myapp.pp file (you can immediately load it with semodule -i myapp.pp). If several modules are defined, make will create all the corresponding .pp files.