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

RHEL 8 Beta offre aux développeurs de nombreuses nouvelles fonctionnalités, dont la liste pourrait prendre des pages, cependant, apprendre de nouvelles choses est toujours mieux dans la pratique, nous proposons donc ci-dessous un atelier sur la création réelle d'une infrastructure d'application basée sur Red Hat Enterprise Linux 8 Beta.

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

Prenons comme base Python, un langage de programmation populaire parmi les développeurs, une combinaison de Django et PostgreSQL, une combinaison assez courante pour créer des applications, et configurons RHEL 8 Beta pour fonctionner avec eux. Ensuite, nous ajouterons quelques ingrédients supplémentaires (non classés).

L'environnement de test va changer, car il est intéressant d'explorer les possibilités d'automatisation, de travailler avec des conteneurs et d'essayer des environnements avec plusieurs serveurs. Pour démarrer un nouveau projet, vous pouvez commencer par créer manuellement un petit prototype simple afin de voir exactement ce qui doit se produire et comment il interagit, puis passer à l'automatisation et à la création de configurations plus complexes. Aujourd'hui, nous parlons de la création d'un tel prototype.

Commençons par déployer l’image de la VM RHEL 8 Beta. Vous pouvez installer une machine virtuelle à partir de zéro ou utiliser l'image invité KVM disponible avec votre abonnement bêta. Lorsque vous utilisez une image invitée, 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 de plus près l'ensemble du processus.

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 bêta, vous pouvez voir que Python 3.6 est disponible, vérifions si c'est effectivement 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 boîte à outils système dans RHEL, alors pourquoi cela se produit-il ?

Le fait est que de nombreux développeurs Python envisagent encore de passer de Python 2 à Python 2, tandis que Python 3 lui-même est en développement actif et que de plus en plus de nouvelles versions apparaissent constamment. Par conséquent, pour répondre au besoin d'outils système stables tout en offrant aux utilisateurs l'accès à diverses nouvelles versions de Python, le système Python a été déplacé dans un nouveau package et a offert la possibilité d'installer à la fois Python 2.7 et 3.6. De plus amples informations sur les modifications et les raisons pour lesquelles elles ont été apportées peuvent être trouvées dans la publication en Le blog de Langdon White (Langdon Blanc).

Ainsi, pour faire fonctionner Python, il vous suffit d'installer deux packages, avec python3-pip inclus en tant que dépendance.

sudo yum install python36 python3-virtualenv

Pourquoi ne pas utiliser les appels directs de module comme le suggère Langdon et installer pip3 ? En gardant à l'esprit l'automatisation à venir, on sait qu'Ansible nécessitera l'installation de pip pour s'exécuter, car le module pip ne prend pas en charge 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 disposer d'un système fonctionnel avec nos autres composants. Il existe de nombreuses options de mise en œuvre disponibles sur Internet. Il existe une version présentée ici, mais les utilisateurs peuvent utiliser leurs propres processus.

Nous installerons les versions PostgreSQL et Nginx disponibles dans RHEL 8 par défaut en utilisant Yum.

sudo yum install nginx postgresql-server

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

Il y a toujours beaucoup de débats sur le choix du bon endroit pour installer les projets Django, mais en cas de doute, vous pouvez toujours vous tourner vers le standard de hiérarchie du système de fichiers Linux. Plus précisément, le FHS indique que /srv est utilisé pour : « stocker les données spécifiques à l'hôte : les données produites par le système, telles que les données et les scripts du serveur Web, les données stockées sur les serveurs FTP et les référentiels du système de contrôle. » versions (apparaissant dans le FHS -2.3 en 2004)."

C'est exactement notre cas, nous mettons donc tout ce dont nous avons besoin dans /srv, qui appartient à l'utilisateur de notre application (utilisateur du cloud).

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éez une base de données, créez un utilisateur, configurez les autorisations. Une chose à garder à l'esprit lors de l'installation initiale de PostgreSQL est le script postgresql-setup installé avec le package postgresql-server. Ce script vous aide à effectuer les tâches de base associées à l'administration du cluster de bases de données, telles que l'initialisation du cluster ou le processus de mise à niveau. Pour configurer une nouvelle instance PostgreSQL sur un système RHEL, nous devons exécuter la commande :

sudo /usr/bin/postgresql-setup -initdb

Vous pouvez ensuite démarrer PostgreSQL en utilisant systemd, créer une base de données et configurer un projet dans Django. N'oubliez pas de redémarrer PostgreSQL après avoir apporté des modifications au fichier de configuration d'authentification client (généralement pg_hba.conf) pour configurer le stockage du mot 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, c'est une bonne idée 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 ? Waouh ?

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 WSGI (Web Server Gateway Interface). Il existe plusieurs combinaisons courantes, par exemple Apache HTTPD avec uWSGI ou Nginx avec Gunicorn.

Le travail de l'interface Web Server Gateway consiste à transmettre les requêtes du serveur Web au framework Web Python. WSGI est une relique du terrible passé de l'époque où les moteurs CGI existaient, et aujourd'hui, WSGI est le standard de facto, quel que soit le serveur Web ou le framework Python utilisé. Mais malgré son utilisation généralisée, il existe encore de nombreuses nuances lorsqu'on travaille avec ces frameworks, et de nombreux choix. Dans ce cas, nous tenterons d'établir une interaction entre Gunicorn et Nginx via un socket.

Puisque ces deux composants sont installés sur le même serveur, essayons d'utiliser un socket UNIX au lieu d'un socket réseau. Puisque la communication nécessite de toute façon un socket, essayons de faire un pas de plus et de configurer 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 unité est créé qui contient une directive ListenStream pointant vers le point auquel le socket UNIX sera créé, puis un fichier unité pour le service dans lequel la directive Requires pointera vers le fichier unité du socket. Ensuite, dans le fichier unité de 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 que vous pouvez utiliser comme base. Nous avons d’abord configuré 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 s'agit simplement de créer des fichiers de configuration de proxy et de configurer un 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 de définir le nom du serveur pour qu'il corresponde à 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;
   }
}

Démarrez le socket Gunicorn et Nginx en utilisant systemd et vous êtes prêt à commencer les tests.

Mauvaise erreur de passerelle ?

Si vous entrez l'adresse dans votre navigateur, vous recevrez très probablement une erreur 502 Bad Gateway. Cela peut être dû à des autorisations de socket UNIX mal configurées 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 directement Gunicorn, nous obtiendrons une réponse vide.

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

Voyons pourquoi cela se produit. Si vous ouvrez le journal, vous verrez probablement que le problème est lié à SELinux. Puisque nous exécutons un démon pour lequel aucune politique n'a été créée, 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 de sang, mais il ne s'agit que de débogage du prototype. Désactivons la vérification juste pour nous assurer que c'est bien là le problème, après quoi nous remettrons tout à sa place.

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

Ainsi, après nous être assurés que tout fonctionne et qu'il n'y a plus de problèmes d'autorisation, nous réactivons SELinux.

sudo setenforce 1

Je ne parlerai pas ici d'audit2allow ou de la création de politiques basées sur des alertes avec sepolgen, car il n'y a pas d'application Django réelle pour le moment, donc il n'y a pas de carte complète de ce à quoi Gunicorn pourrait vouloir accéder et de ce à quoi il devrait refuser l'accès. Par conséquent, il est nécessaire de maintenir SELinux en marche pour protéger le système, tout en permettant à l'application de s'exécuter et de laisser des messages dans le journal d'audit afin que la politique réelle puisse ensuite être créée à partir d'eux.

Spécification de domaines permissifs

Tout le monde n'a pas entendu parler des domaines autorisés dans SELinux, mais ce n'est pas nouveau. Beaucoup ont même travaillé avec eux sans même s’en rendre compte. Lorsqu'une stratégie est créée sur la base de messages d'audit, la stratégie créée représente le domaine résolu. Essayons de créer une politique de permis simple.

Pour créer un domaine autorisé spécifique pour Gunicorn, vous avez besoin d'une sorte de politique et vous devez également marquer les fichiers appropriés. En outre, des outils sont nécessaires pour élaborer de nouvelles politiques.

sudo yum install selinux-policy-devel

Le mécanisme des domaines autorisés est un excellent outil pour identifier les problèmes, en particulier lorsqu'il s'agit d'une ou plusieurs applications personnalisées livrées sans politiques déjà créées. Dans ce cas, la politique de domaine autorisée pour Gunicorn sera aussi simple que possible : déclarer un type principal (gunicorn_t), déclarer un type que nous utiliserons pour marquer plusieurs exécutables (gunicorn_exec_t), puis configurer une transition pour que le système marque correctement 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 à votre 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 sur le socket UNIX utilisé par Gunicorn. Généralement, dans de tels cas, les politiques commencent à changer, mais d’autres défis nous attendent. Vous pouvez également modifier les paramètres du domaine d'un domaine de restriction à un domaine d'autorisation. Déplaçons maintenant httpd_t vers le domaine des autorisations. Cela donnera à Nginx l'accès nécessaire et nous pourrons continuer le travail de débogage.

sudo semanage permissive -a httpd_t

Ainsi, une fois que vous avez réussi à protéger SELinux (vous ne devriez vraiment pas laisser un projet SELinux en mode restreint) et que les domaines d'autorisation sont chargés, vous devez déterminer ce qui doit exactement être marqué comme gunicorn_exec_t pour que tout fonctionne correctement. encore. Essayons de visiter le site Web pour voir les nouveaux messages sur les restrictions d'accès.

sudo ausearch -m AVC -c gunicorn

Vous verrez beaucoup de messages contenant 'comm="gunicorn"' qui font diverses choses sur les fichiers dans /srv/djangoapp, c'est donc évidemment l'une des commandes qui mérite d'être signalée.

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, vous ne verrez aucun processus en cours d'exécution. Il semble que Gunicorn essaie d'accéder à l'interpréteur Python dans notre environnement virtualenv, éventuellement pour exécuter des scripts de travail. Alors maintenant, marquons ces deux fichiers exécutables et vérifions 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

Le service gunicorn devra être redémarré avant que la nouvelle balise puisse être sélectionnée. 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 le navigateur. Vérifiez que les processus ont reçu les étiquettes correctes à l'aide de ps.

ps -efZ | grep gunicorn

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

Si vous regardez maintenant les messages AVC, 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 le meilleur moyen de résoudre de tels problèmes. Mais d’ici là, il est préférable de sécuriser le système et d’obtenir un audit clair et utilisable du projet Django.

sudo ausearch -m AVC

Il s'est avéré!

Un projet Django fonctionnel est apparu avec une interface basée sur Nginx et Gunicorn WSGI. Nous avons configuré Python 3 et PostgreSQL 10 à partir des référentiels RHEL 8 Beta. Vous pouvez désormais 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

Ajouter un commentaire