Atelier RHEL 8 Beta: Création d'applications Web en direct

RHEL 8 Beta offre aux développeurs de nombreuses nouvelles fonctionnalités, dont la liste peut prendre des pages, cependant, il est toujours préférable d'apprendre de nouvelles choses dans la pratique, nous vous suggérons donc de suivre un atelier pour réellement créer une infrastructure d'application basée sur Red Hat Enterprise Linux 8 Beta.



Nous prenons Python, une combinaison de Django et PostgreSQL, un bundle assez courant pour créer des applications, et configurons RHEL 8 Beta pour fonctionner avec eux. Ajoutez ensuite quelques autres ingrédients (non classés).

L'environnement de test va changer, car il est intéressant d'étudier les capacités d'automatisation, de travailler avec des conteneurs et d'essayer des environnements avec plusieurs serveurs. Pour commencer avec un nouveau projet, vous pouvez commencer par créer un petit prototype simple manuellement - de cette façon, vous pouvez voir ce qui doit exactement se produire et comment l'interaction est effectuée, puis passer à l'automatisation et créer des configurations plus complexes. Aujourd'hui, c'est l'histoire de la création d'un tel prototype.

Commençons par déployer l'image de la machine virtuelle RHEL 8 Beta VM. Vous pouvez installer la machine virtuelle à partir de zéro ou utiliser l'image d'invité KVM, disponible avec un abonnement bêta. Lorsque vous utilisez une image invité, vous devrez configurer un CD virtuel, qui contiendra les métadonnées et les données utilisateur pour l'initialisation du cloud (cloud-init). Vous n'avez rien à faire de spécial avec la structure du disque ou les packages disponibles, n'importe quelle configuration fera l'affaire.

Examinons l'ensemble du processus plus en détail.

Installer Django


Avec la dernière version de Django, vous aurez besoin d'un environnement virtuel (virtualenv) avec Python 3.5 ou version ultérieure. Dans les notes pour Beta, vous pouvez voir que Python 3.6 est disponible, vérifions si c'est vrai:

[cloud-user@8beta1 ~]$ python -bash: python: command not found [cloud-user@8beta1 ~]$ python3 -bash: python3: command not found 

Red Hat utilise activement Python comme boîte à outils système dans RHEL, alors pourquoi obtenez-vous ce résultat?

Le fait est que de nombreux développeurs utilisant Python envisagent toujours de passer de Python 2 à Python 3, tandis que Python 3 lui-même est en cours de développement actif, et de plus en plus de nouvelles versions apparaissent constamment. Par conséquent, afin de satisfaire le besoin d'outils système stables, et en même temps d'offrir aux utilisateurs l'accès à diverses nouvelles versions de Python, le système Python a été transféré dans un nouveau package et a permis d'installer à la fois Python 2.7 et 3.6. Plus d'informations sur les changements et pourquoi cela a été fait peuvent être trouvées dans le blog de Langdon White .

Donc, pour faire fonctionner Python, vous devez installer seulement deux packages, tandis que python3-pip apparaîtra comme une dépendance.

 sudo yum install python36 python3-virtualenv 

Pourquoi ne pas utiliser les appels de module directs, comme le suggère Langdon, et ne pas installer pip3? En gardant à l'esprit l'automatisation à venir, il est connu qu'Ansible nécessitera un 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 fonctionnel à votre disposition, vous pouvez continuer le processus d'installation de Django et obtenir un système fonctionnel avec nos autres composants. Le réseau présente de nombreuses options de mise en œuvre. Une version est présentée ici, mais les utilisateurs peuvent utiliser leurs propres processus.

Les versions de PostgreSQL et Nginx disponibles dans RHEL 8 seront installées par défaut en utilisant Yum.

 sudo yum install nginx postgresql-server 

PostgreSQL nécessitera psycopg2, mais il doit être disponible uniquement dans l'environnement virtualenv, nous allons donc l'installer en utilisant pip3 avec Django et Gunicorn. Mais nous devons d'abord configurer virtualenv.

Il y a toujours beaucoup de débat sur le choix du bon endroit pour installer les projets Django, mais en cas de doute, vous pouvez toujours vous référer à la norme de hiérarchie du système de fichiers Linux. En particulier, le FHS dit que / srv est utilisé pour: «stocker des données spécifiques à l'hôte - données que le système fournit, par exemple, des données et des scripts provenant de serveurs Web, des données stockées sur des serveurs FTP, ainsi que des référentiels de systèmes de contrôle versions (introduites dans FHS-2.3 en 2004). »

C'est juste notre cas, donc nous mettons tout ce dont nous avons besoin dans / srv, qui appartient à 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 est simple: créer une base de données, créer un utilisateur, configurer les autorisations. Il y a un point à garder à l'esprit lors de la première installation de PostgreSQL - le script postgresql-setup, qui est installé avec le paquet postgresql-server. Ce script vous aide à effectuer des tâches de base liées à l'administration d'un cluster de base de données, telles que l'initialisation d'un cluster ou le processus de mise à jour. Pour configurer une nouvelle instance de PostgreSQL sur le système RHEL, nous devons exécuter la commande:

 sudo /usr/bin/postgresql-setup -initdb 

Après cela, vous pouvez démarrer PostgreSQL à l'aide de systemd, créer une base de données et configurer le projet dans Django. N'oubliez pas de redémarrer PostgreSQL après avoir apporté des modifications au fichier de configuration de l'authentification client (généralement pg_hba.conf) pour configurer le stockage des mots de passe pour l'utilisateur de l'application. Si vous rencontrez d'autres difficultés, assurez-vous de modifier les paramètres IPv4 et IPv6 dans le fichier pg_hba.conf.

 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 configuration de la base de données, vous pouvez démarrer le serveur de développement pour vous assurer que tout fonctionne. Après avoir démarré le serveur de développement, il est agréable de créer un utilisateur administrateur afin de tester la connexion à la base de données.

 ./manage.py runserver 0.0.0.0:8000 ./manage.py createsuperuser 

WSGI? Wai


Le serveur de développement est utile pour les tests, mais pour exécuter l'application, vous devez configurer le serveur et le proxy appropriés pour l'interface de passerelle de serveur Web (WSGI). Il existe plusieurs bundles courants, par exemple, Apache HTTPD avec uWSGI ou Nginx avec Gunicorn.

Le but de l'interface de passerelle de serveur Web est de rediriger les demandes du serveur Web vers la structure Web Python. WSGI est une sorte d'héritage du terrible passé où les mécanismes CGI étaient utilisés, et aujourd'hui WSGI est en fait la norme, quel que soit le serveur Web ou le cadre Python utilisé. Mais malgré sa large distribution, il existe encore de nombreuses nuances lors de l'utilisation de ces cadres et de nombreux choix. Dans ce cas, nous allons essayer d'établir une interaction entre Gunicorn et Nginx via le 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. Étant donné que la communication nécessite un socket de toute façon, essayons une étape de plus et configurons l'activation du socket pour Gunicorn via systemd.

Le processus de création de services activés par socket est assez simple. Tout d'abord, un fichier d'unité est créé qui contient la directive ListenStream, pointant vers le point auquel le socket UNIX sera créé, puis un fichier d'unité pour le service, où la directive Requirements pointe vers le fichier d'unité de socket. Ensuite, dans le fichier unitaire du service, il ne reste plus qu'à appeler Gunicorn depuis l'environnement virtuel et créer une liaison WSGI pour le socket UNIX et l'application Django.

Voici quelques exemples de fichiers unitaires pouvant servir de base. Tout d'abord, configurez le socket.

 [Unit] Description=Gunicorn WSGI socket [Socket] ListenStream=/run/gunicorn.sock [Install] WantedBy=sockets.target 

Vous devez maintenant configurer le démon Gunicorn.

 [Unit] Description=Gunicorn daemon 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 un fichier de configuration proxy et de configurer un répertoire pour stocker le contenu statique si vous l'utilisez. Dans RHEL, les fichiers de configuration Nginx sont /etc/nginx/conf.d. Vous pouvez y copier l'exemple suivant dans le fichier /etc/nginx/conf.d/default.conf et démarrer le service. Assurez-vous de spécifier nom_serveur en fonction de votre nom d'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; } } 

Exécutez les sockets Gunicorn et Nginx à l'aide de systemd, et vous pouvez commencer les tests.

Erreur de passerelle incorrecte?


Si vous entrez l'adresse dans le navigateur, vous obtiendrez probablement l'erreur 502 Bad Gateway. Cela peut être dû à des autorisations mal configurées pour le socket UNIX ou à des problèmes plus complexes liés au contrôle d'accès dans SELinux.

Dans le journal des erreurs nginx, vous pouvez voir une ligne comme celle-ci:

 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 obtenons une réponse vide.

 curl —unix-socket /run/gunicorn.sock 8beta1.example.com 

Voyons pourquoi cela se produit. Si vous ouvrez le journal, nous verrons très probablement que le problème est lié à SELinux. Puisque nous exécutons un démon pour lequel nous n'avons pas créé notre propre politique, il est marqué comme init_t. Testons cette théorie dans la pratique.

 sudo setenforce 0 

Tout cela peut provoquer des critiques et des larmes sanglantes, mais cela ne fait que déboguer le prototype. Nous désactivons le chèque uniquement pour nous assurer que c'est bien le problème, après quoi nous retournerons tout à leur place.

En actualisant la page dans le navigateur ou en redémarrant notre commande curl, vous pouvez voir la page de test de Django.

Donc, en nous assurant que tout fonctionne et qu'il n'y a plus de problèmes d'autorisations, nous réactivons SELinux.

 sudo setenforce 1 

Il n'y aura pas de discussion sur audit2allow et la création de politiques basées sur des alertes utilisant sepolgen, car il n'y a actuellement aucune vraie application Django, il n'y a pas de carte complète de ce que Gunicorn pourrait vouloir accéder, et quel accès devrait être refusé. Par conséquent, il est nécessaire de conserver SELinux pour protéger le système, et en même temps permettre à l'application de démarrer et de laisser des messages dans le journal d'audit afin que vous puissiez ensuite créer une véritable stratégie basée sur eux.

Spécification des domaines autorisés


Tout le monde n'a pas entendu parler des domaines autorisés dans SELinux, mais il n'y a rien de nouveau en eux. Beaucoup ont même travaillé avec eux, sans s'en rendre compte eux-mêmes. Lorsqu'une stratégie est créée sur la base de messages d'audit, la stratégie créée est un domaine autorisé. Essayons de créer la politique permissive la plus simple.

Pour créer un domaine autorisé spécifique pour Gunicorn, vous avez besoin d'une certaine politique, et vous devrez également marquer les fichiers appropriés. De plus, des outils sont nécessaires pour assembler de nouvelles politiques.

 sudo yum install selinux-policy-devel 

Le mécanisme de domaine résolu est un excellent outil pour identifier les problèmes, en particulier lorsqu'il s'agit d'une application personnalisée ou d'applications sans politiques déjà créées. Dans ce cas, la stratégie de domaine autorisée pour Gunicorn sera aussi simple que possible - déclarez le type principal (gunicorn_t), déclarez le type que nous utiliserons pour marquer plusieurs fichiers exécutables (gunicorn_exec_t), puis configurez la transition pour que le système marque correctement les processus en cours d'exécution . La dernière ligne définit la stratégie comme activé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; 

Vous pouvez compiler ce fichier de stratégie et l'ajouter 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 que ce à quoi notre démon inconnu accède.

 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 empêche Nginx d'écrire des données dans le socket UNIX utilisé par Gunicorn. Habituellement, dans de tels cas, les politiciens commencent à changer, mais il y a d'autres tâches à accomplir. Vous pouvez également modifier les paramètres du domaine en le transformant d'un domaine de restriction en un domaine d'autorisation. Déplacez maintenant httpd_t vers le domaine d'autorisation. Cela fournira à Nginx l'accès nécessaire et nous pourrons continuer à travailler sur le débogage.

 sudo semanage permissive -a httpd_t 

Ainsi, lorsqu'il a été possible de maintenir la protection SELinux (en fait, vous ne devez pas laisser le projet avec SELinux en mode de restriction) et que les domaines d'autorisation sont chargés, vous devez savoir ce qui doit être marqué exactement comme gunicorn_exec_t pour que tout fonctionne à nouveau comme prévu. Essayons d'accéder au site Web pour voir de nouveaux messages sur les restrictions d'accès.

 sudo ausearch -m AVC -c gunicorn 

Vous pouvez voir beaucoup de messages contenant 'comm = "gunicorn"' qui effectuent diverses actions sur les fichiers dans / srv / djangoapp, donc évidemment ce n'est qu'une des commandes que vous devez marquer.

Mais en plus, un message comme celui-ci 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 d'exécution n'apparaîtra. Gunicorn semble essayer d'accéder à l'interpréteur Python dans notre environnement virtualenv, peut-être pour exécuter des scripts de travail (travailleurs). Alors maintenant, marquons ces deux exécutables et voyons 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 

Vous devrez redémarrer le service gunicorn afin de pouvoir sélectionner une nouvelle étiquette. Vous pouvez le redémarrer immédiatement ou arrêter le service et laisser le socket le démarrer lorsque vous ouvrez le site dans un navigateur. Assurez-vous que les processus obtiennent les étiquettes correctes en utilisant ps.

 ps -efZ | grep gunicorn 

N'oubliez pas de créer une politique SELinux normale plus tard!

Si vous regardez 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 de quel type d'accès une application réelle a besoin, vous pouvez rapidement trouver la meilleure façon de résoudre ces problèmes. Mais jusque-là, il vaut mieux que le système soit protégé, et afin d'obtenir un audit clair et utilisable par le projet Django.

 sudo ausearch -m AVC 

Ça s'est avéré!


Un projet Django fonctionnel avec un frontend sur Nginx et Gunicorn WSGI est apparu. Nous avons configuré Python 3 et PostgreSQL 10 à partir des référentiels RHEL 8 Beta. Vous pouvez maintenant passer à autre chose 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 réglage, améliorer les performances ou même conteneuriser cette configuration.

Source: https://habr.com/ru/post/fr447540/


All Articles