RHEL 8 Beta offre de nombreuses nouvelles possibilités pour les développeurs, dont l'énumération pourrait prendre des pages, mais il est toujours préférable d'apprendre en pratiquant. Nous vous proposons donc ci-dessous de suivre un atelier sur la création réelle d'infrastructures d'applications basées sur Red Hat Enterprise Linux 8 Beta.

Nous allons utiliser Python, un langage de programmation populaire parmi les développeurs, associé à Django et PostgreSQL, une combinaison assez courante pour créer des applications, et nous allons configurer RHEL 8 Beta pour fonctionner avec eux. Ensuite, nous ajouterons quelques (non secrets) ingrédients.
L'environnement de test va Ă©voluer, car il est intĂ©ressant d'explorer les possibilitĂ©s d'automatisation, le travail avec des conteneurs et d'essayer des environnements avec plusieurs serveurs. Pour commencer un nouveau projet, on peut dĂ©buter par la crĂ©ation manuelle d'un petit et simple prototype â cela permet de voir ce qui doit se produire et comment les interactions se dĂ©roulent, avant de passer Ă l'automatisation et Ă la crĂ©ation de configurations plus complexes. Aujourd'hui, nous allons parler de la crĂ©ation d'un tel prototype.
Commençons par déployer l'image de la machine virtuelle RHEL 8 Beta VM. On peut installer la machine virtuelle à partir de zéro ou utiliser l'image invitée KVM disponible avec l'abonnement à la Beta. Si l'on utilise l'image invitée, il sera nécessaire de configurer un CD virtuel contenant les métadonnées et les données utilisateur pour l'initialisation cloud (cloud-init). Il n'est pas nécessaire de modifier quoi que ce soit dans la structure du disque ou les paquets disponibles, n'importe quelle configuration conviendra.
Examinons l'ensemble du processus plus en détail.
Installation de Django
Avec la derniÚre version de Django, un environnement virtuel (virtualenv) avec Python 3.5 ou une version ultérieure est requis. Dans les notes de la Beta, on peut voir que Python 3.6 est disponible, vérifions si c'est réellement le cas :
[cloud-user@8beta1 ~]$ python
-bash: python: command not found
[cloud-user@8beta1 ~]$ python3
-bash: python3: command not found
Red Hat utilise activement Python comme outil systÚme dans RHEL, pourquoi obtient-on donc un tel résultat ?
Le fait est que de nombreux dĂ©veloppeurs utilisant Python rĂ©flĂ©chissent encore Ă la transition de Python 2 vers Python 3, alors que Python 3 est en phase de dĂ©veloppement actif, avec de nouvelles versions qui apparaissent constamment. Ainsi, pour rĂ©pondre Ă la demande d'outils systĂšme stables tout en offrant aux utilisateurs l'accĂšs Ă diverses nouvelles versions de Python, Python a Ă©tĂ© dĂ©placĂ© dans un nouveau paquet, permettant l'installation Ă la fois de Python 2.7 et 3.6. Des informations plus dĂ©taillĂ©es sur les changements et les raisons de cette dĂ©cision peuvent ĂȘtre trouvĂ©es dans la publication sur (Langdon White).
Ainsi, pour obtenir un Python fonctionnel, il suffit d'installer deux paquets, python3-pip sera tiré comme dépendance.
sudo yum install python36 python3-virtualenv
Pourquoi ne pas utiliser d'appels directs au module, comme le propose Langdon, et ne pas installer pip3 ? En gardant à l'esprit l'automatisation à venir, il est connu qu'Ansible nécessite pip installé, car le module pip ne prend pas en charge les environnements virtuels (virtualenvs) avec un exécutable pip personnalisé.
Avec un interprĂ©teur python3 opĂ©rationnel, on peut poursuivre le processus d'installation de Django et obtenir un systĂšme fonctionnel avec nos autres composants. De nombreuses options de mise en Ćuvre sont disponibles en ligne. Ici, une version est prĂ©sentĂ©e, mais les utilisateurs peuvent utiliser leurs propres processus.
Les versions de PostgreSQL et Nginx disponibles par défaut dans RHEL 8 seront installées à l'aide de Yum.
sudo yum install nginx postgresql-server
Pour PostgreSQL, psycopg2 sera nĂ©cessaire, mais il doit ĂȘtre disponible uniquement dans l'environnement virtualenv, donc nous allons l'installer avec pip3, tout en installant Django et Gunicorn. Mais d'abord, nous devons configurer virtualenv.
Lorsqu'il s'agit de choisir le bon emplacement pour l'installation des projets Django, il y a toujours beaucoup de débats. Cependant, en cas de doutes, on peut toujours se référer à la norme Linux Filesystem Hierarchy Standard. En particulier, le FHS stipule que /srv est utilisé pour : « le stockage de données spécifiques à un hÎte, c'est-à -dire des données générées par le systÚme, comme les données et scripts des serveurs web, les données stockées sur des serveurs FTP, ainsi que les dépÎts des systÚmes de contrÎle de version (introduits dans FHS-2.3 en 2004) ».
C'est exactement notre cas, donc nous rassemblons tout le nécessaire dans /srv, dont le propriétaire est notre utilisateur d'application (cloud-user).
sudo mkdir /srv/djangoapp
sudo chown cloud-user:cloud-user /srv/djangoapp
cd /srv/djangoapp
virtualenv django
source django/bin/activate
pip3 install django gunicorn psycopg2
./django-admin startproject djangoapp /srv/djangoapp
La configuration de PostgreSQL et Django n'est pas compliquĂ©e : nous crĂ©ons une base de donnĂ©es, un utilisateur, et configurons les permissions. Il y a un point Ă garder Ă l'esprit lors de l'installation initiale de PostgreSQL â c'est le script postgresql-setup, qui est installĂ© avec le paquet postgresql-server. Ce script aide Ă effectuer des tĂąches de base liĂ©es Ă l'administration du cluster de bases de donnĂ©es, comme l'initialisation du cluster ou le processus de mise Ă jour. Pour configurer une nouvelle instance PostgreSQL sur le systĂšme RHEL, nous devons exĂ©cuter la commande :
sudo /usr/bin/postgresql-setup -initdb
AprÚs cela, nous pouvons démarrer PostgreSQL à l'aide de systemd, créer la base de données et configurer le projet dans Django. N'oubliez pas de redémarrer PostgreSQL aprÚs avoir modifié le fichier de configuration d'authentification du client (généralement pg_hba.conf) pour configurer le stockage du mot de passe pour l'utilisateur d'application. Si vous rencontrez d'autres difficultés, assurez-vous que les réglages IPv4 et IPv6 dans le fichier pg_hba.conf ont été modifiés.
systemctl enable --now postgresql
sudo -u postgres psql
postgres=# create database djangoapp;
postgres=# create user djangouser with password 'qwer4321';
postgres=# alter role djangouser set client_encoding to 'utf8';
postgres=# alter role djangouser set default_transaction_isolation to 'read committed';
postgres=# alter role djangouser set timezone to 'utc';
postgres=# grant all on DATABASE djangoapp to djangouser;
postgres=# q
Dans le fichier /var/lib/pgsql/data/pg_hba.conf :
# IPv4 local connections:
host all all 0.0.0.0/0 md5
# IPv6 local connections:
host all all ::1/128 md5
Dans le fichier /srv/djangoapp/settings.py :
# Database
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': '{{ db_name }}',
'USER': '{{ db_user }}',
'PASSWORD': '{{ db_password }}',
'HOST': '{{ db_host }}',
}
}
AprÚs avoir configuré le fichier settings.py dans le projet et configuré la base de données, nous pouvons démarrer le serveur de développement pour nous assurer que tout fonctionne. Une fois le serveur de développement démarré, il est bon de créer un utilisateur admin pour tester la connexion à la base de données.
./manage.py runserver 0.0.0.0:8000
./manage.py createsuperuser
WSGI ? Qu'est-ce que c'est ?
Le serveur de développement est utile pour les tests, mais pour exécuter l'application, il est nécessaire de configurer le serveur et le proxy appropriés pour le Web Server Gateway Interface (WSGI). Il existe plusieurs configurations courantes, comme Apache HTTPD avec uWSGI ou Nginx avec Gunicorn.
La tĂąche du Web Server Gateway Interface est de rediriger les requĂȘtes de serveur web au framework web Python. WSGI est un hĂ©ritage d'un passĂ© difficile, lorsqu'on utilisait des mĂ©canismes CGI, et aujourd'hui WSGI est en fait devenu la norme, quel que soit le serveur web ou le framework Python utilisĂ©s. Cependant, malgrĂ© sa large adoption, il y a encore de nombreux dĂ©tails Ă considĂ©rer lors de l'utilisation de ces frameworks, ainsi qu'un grand nombre d'options disponibles. Dans ce cas, nous allons essayer de configurer l'interaction entre Gunicorn et Nginx via un socket.
Ătant donnĂ© que ces deux composants sont installĂ©s sur le mĂȘme serveur, nous allons essayer d'utiliser un socket UNIX au lieu d'un socket rĂ©seau. Comme un socket est nĂ©cessaire pour la communication, essayons de faire un pas supplĂ©mentaire en configurant l'activation du socket pour Gunicorn via systemd.
Le processus de crĂ©ation de services activĂ©s par des sockets (socket activated services) est relativement simple. Un fichier unitaire est d'abord créé, contenant la directive ListenStream, indiquant le point oĂč le socket UNIX sera créé, puis un fichier unitaire pour le service, oĂč la directive Requires fera rĂ©fĂ©rence au fichier unitaire du socket. Ensuite, il ne reste plus qu'Ă appeler Gunicorn depuis l'environnement virtuel et Ă crĂ©er un lien WSGI pour le socket UNIX et l'application Django dans le fichier unitaire du service.
Voici quelques exemples de fichiers unitaires que vous pouvez utiliser comme base. Commençons par configurer le socket.
[Unit]
Description=Socket WSGI de Gunicorn
[Socket]
ListenStream=\/run\/gunicorn.sock
[Install]
WantedBy=sockets.target
Maintenant, il est nécessaire de configurer le démon Gunicorn.
[Unit]
Description=Démon Gunicorn
Requires=gunicorn.socket
After=network.target
[Service]
User=cloud-user
Group=cloud-user
WorkingDirectory=\/srv\/djangoapp
ExecStart=\/srv\/djangoapp\/django\/bin\/gunicorn
âaccess-logfile -
âworkers 3
âbind unix:gunicorn.sock djangoapp.wsgi
[Install]
WantedBy=multi-user.target
Pour Nginx, il suffit de créer des fichiers de configuration proxy et de configurer le répertoire pour stocker le contenu statique, si vous en utilisez un. Dans RHEL, les fichiers de configuration Nginx se trouvent dans \/etc\/nginx\/conf.d. Vous pouvez copier l'exemple suivant dans le fichier \/etc\/nginx\/conf.d\/default.conf, et démarrer le service. Assurez-vous d'avoir correctement spécifié server_name conformément au nom de votre hÎte.
server {
listen 80;
server_name 8beta1.example.com;
location = \/favicon.ico { access_log off; log_not_found off; }
location \/static\/ {
root \/srv\/djangoapp;
}
location \/ {
proxy_set_header Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http:\/\/unix:\/run\/gunicorn.sock;
}
}
Démarrez le socket Gunicorn et Nginx avec systemd, et vous pouvez commencer à tester.
Erreur Bad Gateway ?
Si vous entrez l'adresse dans le navigateur, il est fort probable que vous receviez une erreur 502 Bad Gateway. Cela peut ĂȘtre causĂ© par des permissions mal configurĂ©es pour le socket UNIX, ou par des problĂšmes plus complexes liĂ©s Ă la gestion des accĂšs dans SELinux.
Dans le journal des erreurs nginx, on peut trouver une ligne de ce type :
2018/12/18 15:38:03 [crit] 12734#0: *3 connect() to unix:/run/gunicorn.sock failed (13: Permission denied) while connecting to upstream, client: 192.168.122.1, server: 8beta1.example.com, request: "GET / HTTP/1.1", upstream: "http://unix:/run/gunicorn.sock:/", host: "8beta1.example.com"
Si nous testons Gunicorn directement, nous obtiendrons une réponse vide.
curl --unix-socket /run/gunicorn.sock 8beta1.example.com
Comprenons pourquoi cela se produit. Si nous ouvrons le journal, nous verrons probablement que le problÚme est lié à SELinux. Comme nous exécutons un démon pour lequel aucune politique n'a été créée, il est marqué comme init_t. Vérifions cette théorie en pratique.
sudo setenforce 0
Tout cela peut susciter des critiques et des larmes amÚres, mais ce n'est qu'un débogage de prototype. Nous désactiverons la vérification juste pour nous assurer que le problÚme vient bien de là , aprÚs quoi nous remettrons tout en place.
En actualisant la page dans le navigateur ou en redémarrant notre commande curl, nous pourrons voir la page de test de Django.
Ainsi, ayant vérifié que tout fonctionne et qu'il n'y a plus de problÚmes de permissions, nous réactivons SELinux.
sudo setenforce 1
Ici, il n'y aura pas de discussion sur audit2allow et sur la crĂ©ation de politiques basĂ©es sur des alertes via sepolgen, car Ă l'heure actuelle, il n'y a pas d'application Django rĂ©elle, donc pas de carte complĂšte de ce Ă quoi Gunicorn pourrait vouloir accĂ©der, et ce Ă quoi cet accĂšs devrait ĂȘtre interdit. Il est donc nĂ©cessaire de garder le fonctionnement de SELinux pour protĂ©ger le systĂšme, tout en permettant Ă l'application de se lancer et de laisser des messages dans le journal d'audit, afin de pouvoir ensuite crĂ©er une rĂ©elle politique sur cette base.
Indication des domaines permissifs
Tout le monde n'a pas entendu parler des domaines permissifs dans SELinux, mais il n'y a rien de nouveau Ă leur sujet. Beaucoup ont mĂȘme travaillĂ© avec eux, sans le savoir. Lorsqu'une politique est créée sur la base des messages d'audit, la politique créée reprĂ©sente un domaine permis. Essayons de crĂ©er une politique permissive la plus simple.
Pour créer un domaine autorisé spécifique pour Gunicorn, une certaine politique est requise, ainsi qu'un marquage des fichiers concernés. De plus, des outils sont nécessaires pour assembler de nouvelles politiques.
sudo yum install selinux-policy-devel
Le mécanisme des domaines autorisés est un excellent outil pour identifier des problÚmes, surtout lorsqu'il s'agit d'une application personnalisée ou d'applications fournies sans politiques déjà créées. Dans ce cas, la politique du domaine autorisé pour Gunicorn sera au maximum simple : nous déclarerons le type principal (gunicorn_t), déclarerons le type que nous allons utiliser pour marquer plusieurs fichiers exécutables (gunicorn_exec_t), puis configurerons la transition pour system afin de marquer correctement les processus en cours. La derniÚre ligne établit la politique comme autorisée par défaut au moment de son chargement.
gunicorn.te:
policy_module(gunicorn, 1.0)
type gunicorn_t;
type gunicorn_exec_t;
init_daemon_domain(gunicorn_t, gunicorn_exec_t)
permissive gunicorn_t;
Ce fichier de politique peut ĂȘtre compilĂ© et ajoutĂ© au systĂšme.
make -f /usr/share/selinux/devel/Makefile
sudo semodule -i gunicorn.pp
sudo semanage permissive -a gunicorn_t
sudo semodule -l | grep permissive
Vérifions si SELinux bloque autre chose en plus de ce à quoi s'adresse notre démon inconnu.
sudo ausearch -m AVC
type=AVC msg=audit(1545315977.237:1273): avc: denied { write } for pid=19400 comm="nginx" name="gunicorn.sock" dev="tmpfs" ino=52977 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:var_run_t:s0 tclass=sock_file permissive=0
SELinux ne permet pas Ă Nginx d'Ă©crire des donnĂ©es dans le socket UNIX utilisĂ© par Gunicorn. Dans de tels cas, on commence gĂ©nĂ©ralement Ă modifier les politiques, mais d'autres tĂąches doivent encore ĂȘtre rĂ©solues. On peut Ă©galement changer les paramĂštres du domaine, le transformant d'un domaine de restrictions en un domaine d'autorisations. Nous allons maintenant transfĂ©rer httpd_t vers le domaine des autorisations. Cela garantira l'accĂšs nĂ©cessaire Ă Nginx, et nous pourrons continuer notre travail de dĂ©bogage.
sudo semanage permissive -a httpd_t
Ainsi, une fois que la protection SELinux a Ă©tĂ© maintenue (il ne faut en rĂ©alitĂ© pas laisser un projet avec SELinux en mode restrictions) et que les domaines d'autorisations sont chargĂ©s, il est nĂ©cessaire de dĂ©terminer ce qui doit ĂȘtre marquĂ© comme gunicorn_exec_t pour que tout fonctionne Ă nouveau correctement. Essayons d'accĂ©der au site Web pour voir les nouveaux messages d'erreur d'accĂšs.
sudo ausearch -m AVC -c gunicorn
On peut voir de nombreux messages contenant 'comm="gunicorn"', qui effectuent diverses actions sur les fichiers dans /srv/djangoapp. Il est donc Ă©vident que câest lâune des commandes Ă marquer.
Mais de plus, un message similaire apparaĂźt :
type=AVC msg=audit(1545320700.070:1542): avc: denied { execute } for pid=20704 comm="(gunicorn)" name="python3.6" dev="vda3" ino=8515706 scontext=system_u:system_r:init_t:s0 tcontext=unconfined_u:object_r:var_t:s0 tclass=file permissive=0
Si vous regardez l'état du service gunicorn ou exécutez la commande ps, aucun processus en cours n'apparaßtra. Il semble que gunicorn essaie d'accéder à l'interpréteur Python dans notre environnement virtualenv, probablement pour exécuter les scripts de travail (workers). Nous allons donc marquer ces deux fichiers exécutables et vérifier si nous pouvons ouvrir notre page de test Django.
chcon -t gunicorn_exec_t /srv/djangoapp/django/bin/gunicorn /srv/djangoapp/django/bin/python3.6
Il sera nĂ©cessaire de redĂ©marrer le service gunicorn pour sĂ©lectionner la nouvelle Ă©tiquette. Vous pouvez le redĂ©marrer immĂ©diatement ou arrĂȘter le service et permettre au socket de le relancer lors de lâouverture du site dans le navigateur. Assurez-vous que les processus ont reçu les bonnes Ă©tiquettes en utilisant ps.
ps -efZ | grep gunicorn
N'oubliez pas de créer ensuite une politique SELinux appropriée !
Si vous consultez les messages AVC maintenant, le dernier message contient permissive=1 pour tout ce qui concerne l'application, et permissive=0 pour le reste du systÚme. Si vous comprenez quel accÚs est nécessaire pour l'application réelle, vous pourrez trouver plus rapidement une solution optimale à ces problÚmes. Mais jusqu'à ce moment-là , il est préférable que le systÚme soit protégé et d'obtenir un audit clair et utilisable pour le projet Django.
sudo ausearch -m AVC
C'est réussi !
Un projet Django fonctionnel est apparu avec un frontend sur Nginx et Gunicorn WSGI. Nous avons configurĂ© Python 3 et PostgreSQL 10 Ă partir des dĂ©pĂŽts RHEL 8 Beta. Nous pouvons maintenant avancer et crĂ©er (ou simplement dĂ©ployer) des applications Django ou explorer d'autres outils disponibles dans RHEL 8 Beta pour automatiser le processus de configuration, amĂ©liorer les performances ou mĂȘme conteneuriser cette configuration.
Source : habr.com
