Développez un plug-in pour sudo pour mieux contrôler son utilisation et implémenter (simplement) vos idées originales !

Magazine
Marque
GNU/Linux Magazine
Numéro
259
Mois de parution
septembre 2022
Spécialité(s)


Résumé

Pour déléguer des actions à des « power users », vous utilisez probablement la commande sudo. Mais votre confiance est limitée et vous souhaiteriez avoir une trace des actions effectuées : nous allons voir dans cet article différentes solutions pour y parvenir. En particulier, nous allons découvrir le mécanisme de plug-ins (que l’on peut écrire en Python) qui permet d’enrichir sudo sans restriction !


Body

1. Introduction

Vous connaissez tous l’intérêt de sudo [1] et vous avez pris l’habitude de configurer cet utilitaire à l’aide de la commande visudo qui permet d’éditer, sans erreur de syntaxe, le fichier /etc/sudoers.

Mais connaissez-vous toutes les options que l’on peut y définir ? Êtes-vous sûr que vous tracez toutes les commandes exécutées par les utilisateurs de sudo ?

Nous allons vous aider à y voir plus clair parmi les solutions possibles. Le choix final restera bien sûr entre vos mains !

2. Intégration de sudo et rsyslog

Par défaut, les utilisations de sudo génèrent des logs qui sont émis à syslog (rsyslogd) avec la catégorie (traduction libre de l’anglais facility) authpriv. Sur le serveur suivant (un CentOS 9), les messages de cette catégorie sont écrits dans le fichier /var/log/secure, ce que nous pouvons vérifier :

[jd@rocky-8 ~]$ sudo id
uid=0(root) gid=0(root) groupes=0(root)
[jd@rocky-8 ~]$ sudo tail /var/log/secure
...
Jul 3 16:34:09 rocky-8 sudo[3053]: jd : TTY=pts/2 ; PWD=/home/jd ; USER=root ; COMMAND=/bin/id
[jd@rocky-8 ~]$

Le manuel sudoers(5) nous indique les options du fichier /etc/sudoers qui permettent de modifier ce comportement par défaut. Par exemple, l’option syslog permet de choisir une autre catégorie :

Defaults syslog=local3

Mais sudo peut aussi écrire ses logs dans son propre fichier de logs grâce à l’option logfile :

Defaults logfile=/var/log/sudo.log

Vérifions cela :

[jd@rocky-8 ~]$ sudo id
uid=0(root) gid=0(root) groupes=0(root)
 
[root@rocky-8 ~]# tail /var/log/sudo.log
Jul 3 16:48:43 : jd : TTY=pts/1 ; PWD=/home/jd ; USER=root ; COMMAND=/bin/id

Le fichier de logs a bien été créé et dûment rempli par sudo.

3. Génération de logs avec sudo

Mais que se passe-t-il si l’utilisateur lance la commande su ou bien ouvre un nouveau shell à partir de sudo ? Essayons et regardons ce qui est enregistré dans les logs :

[jd@rocky-8 ~]$ sudo su -
Dernière connexion : ...
[root@rocky-8 ~]# ls /tmp
...contenu du répertoire...
[root@rocky-8 ~]# exit

Les logs ne montrent pas les commandes exécutées à partir de la nouvelle session shell :

[root@rocky-8 ~]# tail /var/log/sudo.log
...
Jul 3 17:09:28 : jd : TTY=pts/2 ; PWD=/home/jd ; USER=root ; COMMAND=/bin/su -
[root@rocky-8 ~]#

On ne voit pas de log concernant la commande « ls /tmp ».

Heureusement, les options log_input et log_output permettent de résoudre ce problème. En activant ces options, sudo va enregistrer les sessions dans le répertoire /var/log/sudo-io (répertoire configurable via l’option iolog-dir). Ajoutons ces options au fichier /etc/sudoers :

Defaults log_input
Defaults log_output

Puis testons :

[jd@rocky-8 ~]$ sudo su -
Dernière connexion : ...
[root@rocky-8 ~]# ls /tmp
...contenu du répertoire...
[root@rocky-8 ~]# exit

Notez que dorénavant, les logs « normaux » affichent une valeur supplémentaire : un TSID qui est un identifiant de session :

[root@rocky-8 ~]# tail -n2 /var/log/sudo.log
Jul 3 17:21:27 : jd : TTY=pts/2 ; PWD=/home/jd ; USER=root ; TSID=00000Z ;
    COMMAND=/bin/su -

La session a bien été enregistrée quelque part dans le répertoire /var/log/sudo-io :

[root@rocky-8 ~]# cd /var/log/sudo-io/00/00/0Z
[root@rocky-8 0Z]# ll
total 28
-rw------- 1 root root 57 3 juil. 17:21 log
-rw------- 1 root root 20 3 juil. 17:21 stderr
-rw------- 1 root root 20 3 juil. 17:21 stdin
-rw------- 1 root root 20 3 juil. 17:21 stdout
-rw------- 1 root root 225 3 juil. 17:21 timing
-rw------- 1 root root 33 3 juil. 17:21 ttyin
-rw------- 1 root root 455 3 juil. 17:21 ttyout

Les commandes exécutées par l’utilisateur jd peuvent être affichées ainsi :

[root@rocky-8 0Z]# gunzip -c ttyin | tr "\r" "\n"
ls /tmp
exit

C’est bien cela : on retrouve notre « ls /tmp » ! Comme vous le voyez, les informations sont stockées en format compressé et sont plutôt destinées à la commande sudoreplay qui permet de rejouer des sessions enregistrées.

Les options log_input et log_output peuvent être surchargées commande par commande avec les tags LOG_INPUT, NOLOG_INPUT, LOG_OUTPUT et NOLOG_OUTPUT. En effet, dans certains cas, l’option log_input peut empêcher un script shell qui lit sur STDIN de fonctionner.

La possibilité d’enregistrer les sous-commandes lancées à partir du shell bash de notre exemple est aussi possible simplement en activant l’option suivante dans le fichier /etc/sudoers :

Defaults log_subcmds

Réessayons la manipulation précédente :

[jd@rocky-8 ~]$ sudo bash
Dernière connexion : ...
[root@rocky-8 ~]# cat /etc/motd
...contenu du répertoire...
[root@rocky-8 ~]# exit

Et maintenant, regardons les logs :

[root@localhost ~]# tail -n2 /var/log/secure
Jul 21 07:50:27 localhost sudo[12419]:      jd : TTY=pts/1 ; PWD=/home/jd@ ; USER=root ; TSID=08 ; COMMAND=/bin/basename /usr/bin/bash
Jul 21 07:50:30 localhost sudo[12419]:      jd : TTY=pts/1 ; PWD=/home/jd@ ; USER=root ; TSID=08 ; COMMAND=/bin/cat /etc/motd

La commande « cat /etc/motd » est bien loggée aussi !

4. Intégration de sudo et PAM & tlog

4.1 Mise en œuvre de pam_tty_audit

Si notre objectif est d’enregistrer toutes les touches tapées au clavier par les utilisateurs de sudo, il existe une solution générale qui utilise le module pam_tty_audit.so. Comme chacun le sait, sudo est « compatible PAM ». Vous pouvez le vérifier à l’aide de la commande suivante (exécutée sur une distribution Rocky 8) :

[root@rocky-8 audit]# sudo -V
Sudo version 1.8.29
Options de configuration : --build=x86_64-redhat-linux-gnu --host=x86_64-redhat-linux-gnu --program-prefix= --disable-dependency-tracking --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/etc --datadir=/usr/share --includedir=/usr/include --libdir=/usr/lib64 --libexecdir=/usr/libexec --localstatedir=/var --sharedstatedir=/var/lib --mandir=/usr/share/man --infodir=/usr/share/info --prefix=/usr --sbindir=/usr/sbin --libdir=/usr/lib64 --docdir=/usr/share/doc/sudo --disable-root-mailer --with-logging=syslog --with-logfac=authpriv --with-pam --with-pam-login --with-editor=/bin/vi --with-env-editor --with-ignore-dot --with-tty-tickets --with-ldap --with-ldap-conf-file=/etc/sudo-ldap.conf --with-selinux --with-passprompt=[sudo] password for %p: --with-linux-audit --with-sssd
La version du greffon de politique de sudoers est 1.8.29
La version de la grammaire du fichier sudoers est 46
 
Chemin d'accès à sudoers : /etc/sudoers
chemin d'accès à nsswitch : /etc/nsswitch.conf
chemin d'accès à ldap.conf : /etc/sudo-ldap.conf
chemin d'accès à ldap.secret : /etc/ldap.secret
Méthodes d'authentification : 'pam'
Mécanisme syslog si syslog est utilisé pour la journalisation des événements : local3
Priorité syslog utilisée lorsque l'authentification de l'utilisateur est réussie : notice
Priorité Syslog utilisée lorsque l'authentification de l'utilisateur a échoué : alert
Ne pas tenir compte de « . » dans $PATH
...
Répertoire dans lequel les informations renvoyées par les opérations d'entrée/sortie seront stockées : /var/log/sudo-io
Nom de service PAM à utiliser : sudo
Nom de service PAM à utiliser pour les interpréteurs de commandes : sudo-i
...
Type de l'enregistrement de l'horodatage de l'authentification : tty
Écrire dans le journal lorsqu'une commande est autorisée par sudoers
Écrire dans le journal lorsqu'une commande est interdite par sudoers
Don't pre-resolve all group names
...
Sudoers I/O plugin version 1.8.29

La configuration de PAM pour sudo est consignée dans le fichier /etc/pam.d/sudo qui ressemble à ceci :

[jd@rocky-8 ~]$ sudo cat /etc/pam.d/sudo
#%PAM-1.0
auth       include      system-auth
account    include      system-auth
password   include      system-auth
session    include      system-auth

Notez que le fichier /etc/pam.d/sudo-i qui est utilisé par PAM lorsque l’utilisateur invoque la commande « sudo -i » inclut le fichier /etc/pam.d/sudo. Nous pouvons donc ignorer ce fichier ici.

[jd@rocky-8 ~]$ sudo cat /etc/pam.d/sudo-i
#%PAM-1.0
auth       include      sudo
account    include      sudo
password   include      sudo
session    optional     pam_keyinit.so force revoke
session    include      sudo

La documentation du module pam_tty_audit indique que ce module ne peut s’appliquer qu’à la rubrique session, ce qui fait sens puisque nous voulons auditer les entrées au clavier après l’ouverture de la session. Nous ajoutons une ligne dans le fichier /etc/pam.d/sudo qui devient celui-ci :

#%PAM-1.0
auth       include      system-auth
account    include      system-auth
password   include      system-auth
session    include      system-auth
session    optional     pam_tty_audit.so enable=*

Le paramètre enable=* indique que nous activons l’audit pour tous les utilisateurs, y compris root.

Maintenant, ouvrons une nouvelle session utilisateur via sudo et exécutons quelques commandes :

[jd@rocky-8 ~]$ sudo su -
Dernière connexion : ...
[root@rocky-8 ~]# cd /etc
[root@rocky-8 etc]# ls passwd*
passwd passwd-
[root@rocky-8 etc]# exit
déconnexion

Les frappes au clavier sont stockées dans le fichier /var/log/audit/audit.log, périodiquement ou bien quand la session se termine, mais pas en temps réel.

Comme on le voit avec la commande suivante, ce sont les valeurs ASCII des touches qui sont stockées :

[root@rocky-8 audit]# grep type=TTY /var/log/audit/audit.log
type=TTY msg=audit(1657027760.651:262): tty pid=12098 uid=0 auid=0 ses=4 major=136 minor=1 comm="bash" data=6364202F6574630D6C73207061737377642A0D657869740DUID="root" AUID="root"

Ce n’est pas très lisible, heureusement, la commande aureport vient à notre secours :

[root@rocky-8 audit]# aureport --tty
 
TTY Report
===============================================
# date time event auid term sess comm data
===============================================
...
18. 05/07/2022 15:29:20 262 0 pts1 4 bash "cd /etc",<ret>,"ls passwd*",<ret>,"exit",<ret>

4.2 Mise en œuvre de tlog

Disponible à partir de la RHEL/CentOS 8, tlog est une solution alternative à pam_tty_audit. Après avoir installé ce package, il faut configurer l’outil d’enregistrement des sessions nommé tlog-rec-session. Cette configuration est facilement réalisable à partir de l’interface web de cockpit (si vous installez aussi le package cockpit-session-recording), ou bien directement avec un éditeur de texte. Le but est de renseigner le contenu du fichier /etc/tlog/tlog-rec-session.conf :

# cat /etc/tlog/tlog-rec-session.conf | jq .
{
  "shell": "/bin/bash",
  "notice": "\nATTENTION! Your session is being recorded!\n\n",
  "latency": 10,
  "payload": 2048,
  "log": {
    "input": true,
    "output": false,
    "window": true
  },
  "limit": {
    "rate": 16384,
    "burst": 32768,
    "action": "pass"
  },
  "file": {
    "path": "/var/log/tlog/users.log"
  },
  "syslog": {
    "facility": "authpriv",
    "priority": "info"
  },
  "journal": {
    "priority": "info",
    "augment": true
  },
  "writer": "file"
}

Deux remarques : nous pouvons choisir le message qui indiquera clairement aux utilisateurs que leur session est enregistrée et nous pouvons choisir le writer, ici, ce sera le fichier /var/log/tlog/users.log. Nous avons donc créé le répertoire /var/log/tlog et avons attribué son propriétaire à tlog:tlog.

Il reste à configurer le démon sssd, invoqué via le module pam_sssd. Le fichier /etc/sssd/conf.d/session-recording.conf contient ces directives :

[session_recording]
scope=some
users=centos, jd
groups=

Redémarrons le service sssd, puis testons en prenant l’identité de l’utilisateur jd :

# systemctl restart sssd
# su - jd
[root@rocky-8 conf.d]# su - jd
Dernière connexion : ...
 
ATTENTION! Your session is being recorded!
 
[jd@rocky-8 ~]$ ls /tmp
...

Toutes les actions sont enregistrées dans le fichier /var/log/tlog/users.log :

# tail -n2 /var/log/tlog/users.log
{"ver":"2.3","host":"rocky-8","rec":"1766ff42c11f423cbcdfe36dbde7572a-df0-19ee7","user":"jd","term":"xterm","session":4,"id":1,"pos":0,"time":1658468743.478,"timing":"=96x51+4797<1+144<1+96<1+392<1+216<1+128<1+216<1+263<1","in_txt":"ls /tmp\r","in_bin":[],"out_txt":"","out_bin":[]}
{"ver":"2.3","host":"rocky-8","rec":"1766ff42c11f423cbcdfe36dbde7572a-df0-19ee7","user":"jd","term":"xterm","session":4,"id":2,"pos":180777,"time":1658468924.255,"timing":"=96x51<1+79<1+672<1+79<1+145<1","in_txt":"exit\r","in_bin":[],"out_txt":"","out_bin":[]}

5. Mise en œuvre du serveur sudo_logsrvd

Résumons ce que nous avons réalisé jusqu’ici :

  • toutes les commandes exécutées via sudo peuvent être envoyées à (r)syslog ;
  • en cas d’ouverture de session via sudo, toutes les frappes au clavier sont envoyées à auditd grâce au module pam_tty_audit ;
  • ou bien nous pouvons mettre en œuvre l’alternative proposée par tlog.

Pouvons-nous encore renforcer la robustesse de notre système en nous prémunissant, par exemple, contre un utilisateur qui, devenu « root », tenterait d’effacer des logs ?

À partir de la version 1.9.0, un serveur de logs dédié à sudo est disponible. Il s’agit de sudo_logsrvd ! Ce serveur reçoit des messages de la part de sudo et peut les stocker localement aussi bien que les relayer vers un autre serveur (un syslog ou un autre sudo_logsrvd). Le serveur est facile à déployer, encore faut-il qu’il soit fourni avec votre distribution ! Sur notre serveur (une CentOS 9), c’est bien le cas, cependant la version proposée (une 1.9.5.p2) est buggée. Nous devons donc recompiler sudo :

# git clone https://github.com/sudo-project/sudo.git
# cd sudo
 
# # on suppose que vous avez installé les outils de compilation
# # ainsi que le package python3-devel, car nous aurons besoin
# # du support de Python au §6 !
# ./configure –enable-python
# make
# make install

La nouvelle version de sudo est dorénavant dans /usr/local/bin.

# /usr/local/bin/sudo –version
Sudo version 1.9.11
...

Pour tester sudo_logsrvd localement et rapidement, nous allons le lancer en avant-plan avec un fichier de configuration minimaliste pour qu’il écoute sur le port 30343 et stocke ses événements dans le répertoire /var/log/sudo-logsrvd :

# cd logsrvd
# cat > logsrvd.conf <<EOF
[server]
listen_address = localhost:30343
 
# le support de TLS est possible (voir "man sudo_logsrvd")
# listen_address = localhost:30444(tls)
 
[iolog]
iolog_dir = /var/log/sudo-logsrvd
EOF
# ./sudo_logsrvd -n -f logsrvd.conf

Il faut maintenant configurer sudo pour qu’il envoie les événements au serveur. À l’aide de visudo, nous ajoutons la ligne suivante au fichier /etc/sudoers :

Defaults log_servers = localhost:30343

Et nous testons :

[jd@rocky-8 ~]$ sudo ls /tmp
...

Les sessions enregistrées peuvent être affichées – voire rejouées – par la commande sudoreplay :

[root@localhost ~]# sudoreplay -d /var/log/sudo-logsrvd/ -l
juil. 21 07:36:08 2022 : jd : HOST=localhost.localdomain ; TTY=/dev/pts/1 ; CWD=/home/jd ; USER=root ; TSID=00/00/02 ; COMMAND=/bin/ls /tmp

Si vous arrêtez le serveur sudo_logsrvd, il devient impossible d’utiliser sudo :

[jd@rocky-8 ~]$ sudo ls /tmp
sudo : impossible de se connecter au serveur de journalisation
sudo : erreur à l’initialisation du greffon E/S sudoers_io

Les paramètres ignore_iolog_errors ou ignore_log_errors permettent d’éviter ce comportement. Il est aussi possible de spécifier plusieurs serveurs de logs pour assurer une forme de haute disponibilité.

6. Écrire son propre plug-in pour sudo

Et si maintenant nous voulions implémenter une idée originale qui n’est pas supportée par les solutions précédentes ?

6.1 Architecture de sudo

Ici, un petit élément d’architecture de sudo s’impose ! Si vous utilisez une version supérieure ou égale à la v1.8 et que vous avez déjà configuré sudo, vous avez probablement uniquement utilisé la commande visudo pour modifier le fichier /etc/sudoers ? Mais vous ne vous êtes probablement pas rendu compte qu’il existait un fichier nommé /etc/sudo.conf ?

Dans ce fichier de configuration, trois directives nous intéressent ici :

Plugin sudoers_policy sudoers.so
Plugin sudoers_io sudoers.so
Plugin sudoers_audit sudoers.so

Que signifient-elles ?

  1. que sudo est modulaire et utilise des plug-ins !
  2. qu’il existe des plug-ins pour implémenter la politique d’accès aux commandes privilégiées, des plug-ins pour gérer les entrées/sorties des commandes exécutées et des plug-ins d’audit qui enregistrent les utilisations fructueuses et erronées de sudo ;
  3. le plug-in par défaut qui s’appelle sudoers.so, possède trois rôles (policy, io et audit), et implémente la politique définie dans le fichier /etc/sudoers.

Pour information, les plug-ins sont situés dans le répertoire /usr/libexec/sudo/.

La documentation officielle de sudo nous indique qu’il existe d’autres catégories de plug-ins :

  • les plug-ins d’approbation qui sont appelés après le plug-in d’application de la politique. Ceux-ci peuvent alors refuser l’action demandée sur la base de contraintes supplémentaires ;
  • les plug-ins de gestion de groupe qui permettent d’appliquer des relations entre utilisateurs et groupes autres que celles définies au niveau du système ou dans le fichier /etc/sudoers lui-même.

La documentation nous apprend aussi qu’à partir de la version 1.9 de sudo, il est possible d’écrire ses propres plug-ins en Python ! Pour connaître votre version de sudo, rappelez-vous que la commande « sudo -V » affiche la version (voir §4).

Nous allons donc écrire un plug-in de gestion des E/S en Python, ainsi nous pourrons aussi capturer les commandes qu’un utilisateur invoquerait à partir d’un shell ouvert par sudo !

6.2 Squelette de notre plug-in

Notre plug-in est écrit sous forme d’une classe qui doit dériver de sudo.Plugin. La méthode __init__ est optionnelle, mais elle nous permet de recevoir d’éventuels paramètres de configuration. La méthode open est appelée avant d’exécuter la commande de l’utilisateur et la méthode close est appelée à la fin de l’exécution de la commande. Pour finir, les méthodes log_ttyin, log_ttyout permettent d’intercepter les entrées clavier et la sortie à l’écran.

sudo est livré avec des exemples de plug-ins écrits en Python (en général situés dans le répertoire /usr/share/doc/sudo/examples/).

Le squelette de notre plug-in, que nous nommerons user_history.py, s’écrit ainsi :

import sudo
from typing import Tuple
import errno
 
 
VERSION = 1.0
 
 
class SudoIOPlugin(sudo.Plugin):
    def __init__(self,
                 user_env: Tuple[str, ...],
                 settings: Tuple[str, ...],
                 version: str,
                 user_info: Tuple[str, ...],
                 plugin_options: Tuple[str]) -> None:
        pass
 
    def __del__(self) -> None:
        pass
 
    def open(self, argv: Tuple[str, ...],
             command_info: Tuple[str, ...]) -> int:
        """Fonction appelée avant l'exécution de la commande souhaitée"""
        print("EXEC", command_info)
 
        # On accepte la commande, car elle a été acceptée par la politique courante
        return sudo.RC.ACCEPT
 
    def log_ttyout(self, buf: str) -> int:
        return sudo.RC.ACCEPT
 
    def log_ttyin(self, buf: str) -> int:
        return sudo.RC.ACCEPT
 
    def close(self, exit_status: int, error: int) -> None:
        """Fonction appelée après l'exécution d'une commande autorisée"""
        if error == 0:
            print("CLOSE", f"Command returned {exit_status}")
        else:
            error_name = errno.errorcode.get(error, "???")
            print("CLOSE", f"Failed to execute, execve returned {error} ({error_name}")
 

Pour intégrer le plug-in à sudo, il suffit de rajouter une ligne au fichier /etc/sudo.conf :

Plugin python_io python_plugin.so ModulePath=/opt/user_history.py ClassName=SudoIOPlugin

Comme vous le voyez, il faut indiquer le type du plug-in (python_io), sa localisation (ModulePath) et le nom de la classe qui le définit (ClassName).

Testons notre plug-in en exécutant une commande simple :

[jd@localhost ~]$ sudo id
EXEC ('command=/bin/id', 'runas_user=root', 'runas_uid=0', 'runas_gid=0', 'runas_groups=0', 'closefrom=3', 'set_utmp=true', 'log_server_keepalive=true', 'log_server_verify=true', 'umask=022')
uid=0(root) gid=0(root) groupes=0(root)
CLOSE Command returned 0

Nous voyons que nous avons bien intercepté la commande demandée (ici, c’était id).

Attention, petit piège ! Quand vous donnez le chemin complet de votre plug-in avec le paramètre ModulePath, dans le fichier /etc/sudo.conf, l’algorithme interne découpe le nom en répertoire + nom de fichier. Puis le nom du répertoire est ajouté à la fin des chemins de recherches utilisés pour les importations de Python. Ainsi, si votre plug-in s’appelle /chemin/vers/audit.py, il ne sera jamais correctement chargé, car il est fort probable que sudo importera un module audit.py situé dans un répertoire plus prioritaire ! Conclusion : prenez toujours des noms « métiers » (le comportement a été remonté au développeur actuel de sudo, Todd C. Miller, qui a ajouté une note sur son blog) !

Pour des raisons de sécurité évidentes, le plug-in doit être propriété de root et accessible en écriture uniquement par root !

6.3 Finalisation de notre plug-in

Nous savons intercepter les commandes exécutées par l’utilisateur de sudo et pour l’instant, nous avons reproduit à l’aide d’un code Python le comportement de l’option log_subcmds présentée au §3. Essayons d’étayer notre plug-in en lui ajoutant une fonctionnalité supplémentaire : nous voulons que les commandes exécutées à l’aide de sudo soient historisées dans un fichier du compte de l’utilisateur et non pas dans l’historique de « root ».

Nous allons donc ajouter des paramètres à notre plug-in :

  • Histfile indiquera le nom du fichier d’historique (on gérera le caractère tilde qui symbolise le répertoire d’accueil de l’utilisateur) ;
  • Prefix permettra d’ajouter un préfixe à la commande pour indiquer qu’elle a été lancée via sudo ;
  • AsComment sera un booléen qui indiquera si la commande doit être précédée d’un caractère dièse (il est impossible que la valeur du Prefix contienne un #, car ce caractère indique dans /etc/sudo.conf le début d’un commentaire!) ;
  • Verbose sera un booléen qui permettra d’activer le mode verbeux/debug.

L’activation du plug-in dans le fichier /etc/sudo.conf devient :

Plugin python_io python_plugin.so ModulePath=/opt/user_history.py ClassName=SudoIOPlugin \  Histfile=~/.sudo_history Prefix=sudo AsComment=True Verbose=False

Pour prendre en compte les paramètres du plug-in, la méthode __init__ s’écrit maintenant :

import sudo
from typing import Tuple, Dict
import errno
import json    # pour un debug plus tard
import logging
import pwd    # utilisé plus tard
 
 
VERSION = 1.0
 
 
class SudoIOPlugin(sudo.Plugin):
    def __init__(self,
                 user_env: Tuple[str, ...],
                 settings: Tuple[str, ...],
                 version: str,
                 user_info: Tuple[str, ...],
                 plugin_options: Tuple[str]) -> None:
        self.user_env = sudo.options_as_dict(user_env)
        self.user_info = sudo.options_as_dict(user_info)
        self.plugin_options = sudo.options_as_dict(plugin_options)
        self.ttyin_buffer = ""
 
        # Prise en compte des paramètres issus de /etc/sudo.conf
        self.histfile = self.plugin_options.get('Histfile', '.sudo_history')
        self.histfile = self._canonicalize(self.histfile, self.user_info)
        self.prefix = "# " if 'AsComment' in self.plugin_options else ""
        self.prefix += self.plugin_options.get('Prefix', '')
 
        # On veut logger sur l'écran de l'utilisateur
        self.logger = logging.getLogger('my_io_plugin')
        self.logger.setLevel(logging.DEBUG if 'Verbose' in self.plugin_options else logging.ERROR)
        formatter = logging.Formatter('%(levelname)s: %(message)s')
        ch = logging.StreamHandler()
        ch.setFormatter(formatter)
        self.logger.addHandler(ch)
 
        self.logger.debug(f"INIT user_env {self.user_env}")
        self.logger.debug(f"INIT user_info {self.user_info}")
        self.logger.debug(f"INIT plugin_options {self.plugin_options}")

La méthode _canonicalize a pour vocation de traiter le tilde dans les noms de fichiers :

    def _canonicalize(self, filename: str, user_info: Dict[str, str]) -> str:
        user = user_info['user']
        try:
            *_, pw_dir, _ = pwd.getpwnam(user)
            filename = filename.replace("~", pw_dir)
        except KeyError:
            self.logger.error(f"ERROR, user {user} does not exist ?")
 
        return filename

La méthode open s’enrichit d’un appel à la méthode interne _log_history qui historise une commande en la préfixant par la valeur du paramètre Prefix :

    def open(self, argv: Tuple[str, ...],
             command_info: Tuple[str, ...]) -> int:
        """Fonction appelée avant l'exécution de la commande souhaitée"""
        self.logger.debug(f"EXEC {' '.join(argv)}")
        self.logger.debug(f"EXEC info {json.dumps(command_info, indent=4)}")
        self._log_history(" ".join(argv))
 
        # On accepte la commande, car elle a été acceptée par la politique courante
        return sudo.RC.ACCEPT
 
 
    def _log_history(self, buffer: str) -> None:
        try:
            with open(self.histfile, "a") as f:
                f.write(self.prefix + ' ' + buffer + "\n")
        except Exception as e:
            self.logger.error(f"Erreur d'écriture dans le fichier {self.histfile}")
            self.logger.error(e)

Il reste à gérer les touches du clavier interceptées par la méthode log_ttyin et là, il va y avoir quand même un problème ! Quand l’utilisateur édite sa commande et utilise la touche <backspace> ou bien les flèches du curseur, la chaîne finale obtenue est incorrecte et ne reflète pas la véritable commande exécutée. Par exemple, si l’utilisateur tape les touches suivantes :

$ cd /tn<backspace>mp

vous obtiendrez la chaîne :

cd /tnmp

… à moins de traiter les touches spéciales !

Le code suivant traite correctement le <backspace>, mais termine le shell lancé par sudo si une flèche de curseur a été tapée. Il ne tient qu’à vous d’enrichir ce code !

    def log_ttyin(self, buf: str) -> int:
    # On interdit les flèches, car on ne peut pas (facilement) obtenir la véritable commande
        if ord(buf[0]) == 27:
            return sudo.RC.REJECT
 
    # On traite le <backspace> pour reconstruire la vraie commande
        if ord(buf[0]) == 127:
            self.ttyin_buffer = self.ttyin_buffer[:-1]
        else:
            self.ttyin_buffer += buf
 
        if buf == '\r':
         # On a capturé la ligne de commande (même en mode sans echo)
            self._log_history(self.ttyin_buffer)
            self.ttyin_buffer = ""
 
        return sudo.RC.ACCEPT

Pour finir, testons notre plug-in :

[jd@localhost ~]$ sudo cat /etc/motd
[jd@localhost ~]$ sudo bash
[root@localhost jd]# ls /tmp
...
[root@localhost jd]# id
uid=0(root) gid=0(root) groupes=0(root) contexte=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[root@localhost jd]# exit

Vérifions le contenu de notre historique des commandes exécutées directement ou indirectement via sudo :

[jd@localhost ~]$ cat .sudo_history
# sudo cat /etc/motd
# sudo bash
# sudo ls /tmp
# sudo id
# sudo exit

L’historique est bien complet et stocké dans un fichier propre à l’utilisateur !

Conclusion

Sans prétendre être exhaustifs, nous avons présenté diverses solutions, de la plus simple à la plus sophistiquée, qui permettent d’enregistrer les commandes exécutées par les utilisateurs, y compris quand ils emploient sudo. Concernant cet utilitaire, nous vous avons aussi montré qu’il était facile de l’enrichir en implémentant vos propres besoins par le biais de plug-ins écrits en Python ! Il ne reste plus qu’à faire preuve d’inventivité !

Références

[1] Site officiel de sudo : https://www.sudo.ws/

[2] Source du plug-in : https://github.com/majeinfo/sudo_plugin



Article rédigé par

Par le(s) même(s) auteur(s)

Le stockage de séries chronologiques avec InfluxDB

Magazine
Marque
Linux Pratique
HS n°
Numéro
53
Mois de parution
février 2022
Spécialité(s)
Résumé

Depuis une dizaine d’années, le mouvement NoSQL s’est largement répandu et de nouveaux types de bases de données sont apparus. Parmi celles-ci, les bases de données dites « orientées-séries-chronologiques » (TSDB pour Time Series Database) ont montré leur intérêt pour stocker et analyser des données horodatées. On les retrouve dans différents domaines : de l’Internet des objets (IoT) à la collecte de métriques serveurs et réseau, en passant par la surveillance d’applications, la mesure de performances… Dans ce marché de niche, InfluxDB apparaît comme une solution leader [1].

Comment tester un rôle Ansible avec Molecule ?

Magazine
Marque
Linux Pratique
Numéro
128
Mois de parution
novembre 2021
Spécialité(s)
Résumé

Depuis quelques années Ansible est devenu la référence des outils d’automatisation. Ansible applique des Playbooks au format YAML sur vos machines virtuelles, vos équipements réseau, vos conteneurs, vos clusters Kubernetes... bref, il se veut universel. Mais rapidement, même avec une infrastructure modeste, vous devez organiser vos Playbooks. Pour cela, vous utilisez des Rôles que souvent vous développez vous-même. Ces Rôles vont forcément évoluer et dès lors comment être sûr qu’ils seront toujours fonctionnels et idempotents ? C’est là qu’intervient Molecule : cet outil va vous permettre de tester et valider vos Rôles et ainsi vous assurer que votre dépôt Git ne contiendra que des Rôles dûment opérationnels !

Plus sûr et plus simple que Docker, connaissez-vous Singularity ?

Magazine
Marque
Linux Pratique
Numéro
128
Mois de parution
novembre 2021
Spécialité(s)
Résumé

Que vous soyez un développeur, un DevOps ou un administrateur système, vous n’avez pas échappé à la « révolution des conteneurs », et parmi les solutions de conteneurisation disponibles vous avez probablement opté pour Docker ! Mais êtes-vous sûr que Docker est toujours la meilleure solution ? La plus adaptée à vos utilisateurs, à vos contraintes de sécurité ? Nous vous proposons de découvrir Singularity comme alternative à Docker.

Les derniers articles Premiums

Les derniers articles Premium

Les nouvelles menaces liées à l’intelligence artificielle

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Sommes-nous proches de la singularité technologique ? Peu probable. Même si l’intelligence artificielle a fait un bond ces dernières années (elle est étudiée depuis des dizaines d’années), nous sommes loin d’en perdre le contrôle. Et pourtant, une partie de l’utilisation de l’intelligence artificielle échappe aux analystes. Eh oui ! Comme tout système, elle est utilisée par des acteurs malveillants essayant d’en tirer profit pécuniairement. Cet article met en exergue quelques-unes des applications de l’intelligence artificielle par des acteurs malveillants et décrit succinctement comment parer à leurs attaques.

Migration d’une collection Ansible à l’aide de fqcn_migration

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Distribuer du contenu Ansible réutilisable (rôle, playbooks) par l’intermédiaire d’une collection est devenu le standard dans l’écosystème de l’outil d’automatisation. Pour éviter tout conflit de noms, ces collections sont caractérisées par un nom unique, formé d’une espace de nom, qui peut-être employé par plusieurs collections (tel qu'ansible ou community) et d’un nom plus spécifique à la fonction de la collection en elle-même. Cependant, il arrive parfois qu’il faille migrer une collection d’un espace de noms à un autre, par exemple une collection personnelle ou communautaire qui passe à un espace de noms plus connus ou certifiés. De même, le nom même de la collection peut être amené à changer, si elle dépasse son périmètre d’origine ou que le produit qu’elle concerne est lui-même renommé.

Mise en place d'Overleaf Community pour l’écriture collaborative au sein de votre équipe

Magazine
Marque
Contenu Premium
Spécialité(s)
Résumé

Si vous utilisez LaTeX pour vos documents, vous connaissez vraisemblablement Overleaf qui vous permet de rédiger de manière collaborative depuis n’importe quel poste informatique connecté à Internet. Cependant, la version gratuite en ligne souffre de quelques limitations et le stockage de vos projets est externalisé chez l’éditeur du logiciel. Si vous désirez maîtriser vos données et avoir une installation locale de ce bel outil, cet article est fait pour vous.

Les listes de lecture

9 article(s) - ajoutée le 01/07/2020
Vous désirez apprendre le langage Python, mais ne savez pas trop par où commencer ? Cette liste de lecture vous permettra de faire vos premiers pas en découvrant l'écosystème de Python et en écrivant de petits scripts.
11 article(s) - ajoutée le 01/07/2020
La base de tout programme effectuant une tâche un tant soit peu complexe est un algorithme, une méthode permettant de manipuler des données pour obtenir un résultat attendu. Dans cette liste, vous pourrez découvrir quelques spécimens d'algorithmes.
10 article(s) - ajoutée le 01/07/2020
À quoi bon se targuer de posséder des pétaoctets de données si l'on est incapable d'analyser ces dernières ? Cette liste vous aidera à "faire parler" vos données.
Voir les 129 listes de lecture

Abonnez-vous maintenant

et profitez de tous les contenus en illimité

Je découvre les offres

Déjà abonné ? Connectez-vous