Workshop RHEL 8 Beta: Construirea de aplicații web funcționale

RHEL 8 Beta oferă dezvoltatorilor multe funcții noi, a căror listare ar putea dura pagini, cu toate acestea, învățarea lucrurilor noi este întotdeauna mai bună în practică, așa că mai jos oferim un atelier despre crearea efectivă a unei infrastructuri de aplicații bazate pe Red Hat Enterprise Linux 8 Beta.

Workshop RHEL 8 Beta: Construirea de aplicații web funcționale

Să luăm ca bază Python, un limbaj de programare popular printre dezvoltatori, o combinație de Django și PostgreSQL, o combinație destul de comună pentru crearea de aplicații, și să configuram RHEL 8 Beta să funcționeze cu ele. Apoi vom mai adăuga câteva ingrediente (neclasificate).

Mediul de testare se va schimba, deoarece este interesant de explorat posibilitățile de automatizare, lucrul cu containere și încercarea de medii cu mai multe servere. Pentru a începe cu un nou proiect, puteți începe prin a crea manual un prototip mic și simplu, astfel încât să puteți vedea exact ce trebuie să se întâmple și cum interacționează, apoi să treceți la automatizare și crearea unor configurații mai complexe. Astăzi vorbim despre crearea unui astfel de prototip.

Să începem prin a implementa imaginea RHEL 8 Beta VM. Puteți instala o mașină virtuală de la zero sau puteți utiliza imaginea invitată KVM disponibilă cu abonamentul dvs. Beta. Când utilizați o imagine de invitat, va trebui să configurați un CD virtual care va conține metadate și date utilizator pentru inițializarea în cloud (cloud-init). Nu trebuie să faceți nimic special cu structura discului sau cu pachetele disponibile; orice configurație va face bine.

Să aruncăm o privire mai atentă asupra întregului proces.

Instalarea Django

Cu cea mai nouă versiune de Django, veți avea nevoie de un mediu virtual (virtualenv) cu Python 3.5 sau o versiune ulterioară. În notele Beta, puteți vedea că Python 3.6 este disponibil, să verificăm dacă acesta este într-adevăr cazul:

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

Red Hat folosește activ Python ca set de instrumente de sistem în RHEL, deci de ce rezultă acest lucru?

Faptul este că mulți dezvoltatori Python încă se gândesc la tranziția de la Python 2 la Python 2, în timp ce Python 3 în sine este în curs de dezvoltare activă, iar din ce în ce mai multe versiuni noi apar în mod constant. Prin urmare, pentru a satisface nevoia de instrumente de sistem stabile, oferind în același timp utilizatorilor acces la diferite versiuni noi de Python, sistemul Python a fost mutat într-un pachet nou și a oferit posibilitatea de a instala atât Python 2.7, cât și 3.6. Mai multe informații despre modificări și de ce au fost făcute pot fi găsite în publicația din Blogul lui Langdon White (Langdon White).

Deci, pentru a funcționa cu Python, trebuie doar să instalați două pachete, cu python3-pip inclus ca dependență.

sudo yum install python36 python3-virtualenv

De ce să nu folosiți apelurile directe la module așa cum sugerează Langdon și să instalați pip3? Ținând cont de viitoarea automatizare, se știe că Ansible va necesita instalarea pip pentru a rula, deoarece modulul pip nu acceptă virtualenvs cu un executabil pip personalizat.

Având la dispoziție un interpret python3 funcțional, puteți continua procesul de instalare Django și aveți un sistem funcțional împreună cu celelalte componente ale noastre. Există multe opțiuni de implementare disponibile pe Internet. Există o versiune prezentată aici, dar utilizatorii își pot folosi propriile procese.

Vom instala versiunile PostgreSQL și Nginx disponibile în RHEL 8 implicit folosind Yum.

sudo yum install nginx postgresql-server

PostgreSQL va necesita psycopg2, dar trebuie să fie disponibil doar într-un mediu virtualenv, așa că îl vom instala folosind pip3 împreună cu Django și Gunicorn. Dar mai întâi trebuie să setăm virtualenv.

Există întotdeauna o mulțime de dezbateri pe tema alegerii locului potrivit pentru a instala proiectele Django, dar atunci când aveți îndoieli, puteți oricând să apelați la standardul Linux Filesystem Hierarchy. Mai exact, FHS spune că /srv este folosit pentru: „stocarea datelor specifice gazdei — date pe care sistemul le produce, cum ar fi datele și scripturile de server web, datele stocate pe serverele FTP și arhivele sistemului de control.” versiuni (care apar în FHS -2.3 în 2004)."

Acesta este exact cazul nostru, așa că punem tot ce avem nevoie în /srv, care este deținut de utilizatorul aplicației noastre (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

Configurarea PostgreSQL și Django este ușoară: creați o bază de date, creați un utilizator, configurați permisiunile. Un lucru de reținut atunci când instalați inițial PostgreSQL este scriptul postgresql-setup care este instalat cu pachetul postgresql-server. Acest script vă ajută să efectuați sarcini de bază asociate cu administrarea clusterului bazei de date, cum ar fi inițializarea clusterului sau procesul de actualizare. Pentru a configura o nouă instanță PostgreSQL pe un sistem RHEL, trebuie să rulăm comanda:

sudo /usr/bin/postgresql-setup -initdb

Apoi puteți porni PostgreSQL folosind systemd, puteți crea o bază de date și puteți configura un proiect în Django. Amintiți-vă să reporniți PostgreSQL după ce faceți modificări în fișierul de configurare a autentificării clientului (de obicei pg_hba.conf) pentru a configura stocarea parolei pentru utilizatorul aplicației. Dacă întâmpinați alte dificultăți, asigurați-vă că modificați setările IPv4 și IPv6 din fișierul 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

În fișierul /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

În fișierul /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 }}',
   }
}

După configurarea fișierului settings.py în proiect și configurarea bazei de date, puteți porni serverul de dezvoltare pentru a vă asigura că totul funcționează. După pornirea serverului de dezvoltare, este o idee bună să creați un utilizator admin pentru a testa conexiunea la baza de date.

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

WSGI? Wai?

Serverul de dezvoltare este util pentru testare, dar pentru a rula aplicația trebuie să configurați serverul și proxy-ul corespunzător pentru interfața Web Server Gateway (WSGI). Există mai multe combinații comune, de exemplu, Apache HTTPD cu uWSGI sau Nginx cu Gunicorn.

Sarcina interfeței Web Server Gateway este de a transmite cereri de la serverul web către cadrul web Python. WSGI este o relicvă a trecutului teribil când existau motoarele CGI, iar astăzi WSGI este standardul de facto, indiferent de serverul web sau framework-ul Python folosit. Dar, în ciuda utilizării pe scară largă, există încă multe nuanțe atunci când lucrați cu aceste cadre și multe opțiuni. În acest caz, vom încerca să stabilim interacțiunea între Gunicorn și Nginx printr-un socket.

Deoarece ambele componente sunt instalate pe același server, să încercăm să folosim un socket UNIX în loc de un socket de rețea. Deoarece comunicarea necesită un socket în orice caz, să încercăm să mai facem un pas și să configuram activarea socket-ului pentru Gunicorn prin systemd.

Procesul de creare a serviciilor activate prin socket este destul de simplu. Mai întâi, este creat un fișier unitar care conține o directivă ListenStream care indică punctul în care va fi creat socket-ul UNIX, apoi un fișier unitar pentru serviciul în care directiva Requires va indica fișierul unității socket. Apoi, în fișierul unității de serviciu, tot ce rămâne este să apelați Gunicorn din mediul virtual și să creați o legătură WSGI pentru socket-ul UNIX și aplicația Django.

Iată câteva exemple de fișiere unitare pe care le puteți folosi ca bază. Mai întâi am configurat priza.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Acum trebuie să configurați demonul 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

Pentru Nginx, este o chestiune simplă de a crea fișiere de configurare proxy și de a configura un director pentru a stoca conținut static dacă utilizați unul. În RHEL, fișierele de configurare Nginx sunt localizate în /etc/nginx/conf.d. Puteți copia următorul exemplu în fișierul /etc/nginx/conf.d/default.conf și porniți serviciul. Asigurați-vă că setați server_name pentru a se potrivi cu numele dvs. de gazdă.

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;
   }
}

Porniți priza Gunicorn și Nginx folosind systemd și sunteți gata să începeți testarea.

Eroare de gateway greșită?

Dacă introduceți adresa în browser, cel mai probabil veți primi o eroare 502 Bad Gateway. Poate fi cauzată de permisiunile pentru socket UNIX configurate incorect sau poate fi din cauza unor probleme mai complexe legate de controlul accesului în SELinux.

În jurnalul de erori nginx puteți vedea o linie ca aceasta:

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"

Dacă testăm Gunicorn direct, vom obține un răspuns gol.

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

Să ne dăm seama de ce se întâmplă asta. Dacă deschideți jurnalul, cel mai probabil veți vedea că problema este legată de SELinux. Deoarece rulăm un daemon pentru care nu a fost creată nicio politică, acesta este marcat ca init_t. Să testăm această teorie în practică.

sudo setenforce 0

Toate acestea pot provoca critici și lacrimi de sânge, dar aceasta este doar depanarea prototipului. Să dezactivăm verificarea doar pentru a ne asigura că aceasta este problema, după care vom întoarce totul la locul său.

Reîmprospătând pagina în browser sau rulând din nou comanda noastră curl, puteți vedea pagina de test Django.

Deci, după ce ne-am asigurat că totul funcționează și că nu mai există probleme de permisiuni, activăm din nou SELinux.

sudo setenforce 1

Nu voi vorbi aici despre audit2allow sau despre crearea de politici bazate pe alerte cu sepolgen, deoarece nu există nicio aplicație Django reală în acest moment, deci nu există o hartă completă a ceea ce ar putea dori Gunicorn să acceseze și la ce ar trebui să interzică accesul. Prin urmare, este necesar să mențineți SELinux în funcțiune pentru a proteja sistemul, permițând în același timp aplicației să ruleze și să lase mesaje în jurnalul de audit, astfel încât politica reală să poată fi apoi creată din acestea.

Specificarea domeniilor permisive

Nu toată lumea a auzit de domenii permise în SELinux, dar nu sunt nimic nou. Mulți chiar au lucrat cu ei fără să-și dea seama. Când o politică este creată pe baza mesajelor de audit, politica creată reprezintă domeniul rezolvat. Să încercăm să creăm o politică simplă de autorizare.

Pentru a crea un anumit domeniu permis pentru Gunicorn, aveți nevoie de un fel de politică și, de asemenea, trebuie să marcați fișierele corespunzătoare. În plus, sunt necesare instrumente pentru a aduna noi politici.

sudo yum install selinux-policy-devel

Mecanismul de domenii permise este un instrument excelent pentru identificarea problemelor, mai ales atunci când vine vorba de o aplicație personalizată sau aplicații care sunt livrate fără politici deja create. În acest caz, politica de domeniu permisă pentru Gunicorn va fi cât se poate de simplă - declarați un tip principal (gunicorn_t), declarați un tip pe care îl vom folosi pentru a marca mai multe executabile (gunicorn_exec_t) și apoi configurați o tranziție pentru ca sistemul să marcheze corect rularea proceselor. Ultima linie setează politica ca fiind activată în mod implicit în momentul în care este încărcată.

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;

Puteți compila acest fișier de politică și îl puteți adăuga la sistemul dvs.

make -f /usr/share/selinux/devel/Makefile
sudo semodule -i gunicorn.pp

sudo semanage permissive -a gunicorn_t
sudo semodule -l | grep permissive

Să verificăm dacă SELinux blochează altceva decât ceea ce accesează demonul nostru necunoscut.

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 împiedică Nginx să scrie date în socket-ul UNIX folosit de Gunicorn. De obicei, în astfel de cazuri, politicile încep să se schimbe, dar mai sunt și alte provocări. De asemenea, puteți modifica setările domeniului de la un domeniu de restricție la un domeniu de permisiuni. Acum să mutăm httpd_t în domeniul permisiunilor. Acest lucru îi va oferi lui Nginx accesul necesar și putem continua cu lucrările de depanare ulterioare.

sudo semanage permissive -a httpd_t

Deci, odată ce ați reușit să păstrați SELinux protejat (nu ar trebui să lăsați un proiect SELinux în modul restricționat) și domeniile de permisiuni sunt încărcate, trebuie să vă dați seama ce trebuie să fie marcat ca gunicorn_exec_t pentru ca totul să funcționeze corect din nou. Să încercăm să accesăm site-ul web pentru a vedea mesaje noi despre restricțiile de acces.

sudo ausearch -m AVC -c gunicorn

Veți vedea o mulțime de mesaje care conțin „comm="gunicorn"” care fac diverse lucruri pe fișierele din /srv/djangoapp, așa că aceasta este, evident, una dintre comenzile care merită semnalate.

Dar, în plus, apare un mesaj ca acesta:

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

Dacă vă uitați la starea serviciului gunicorn sau rulați comanda ps, nu veți vedea niciun proces care rulează. Se pare că gunicorn încearcă să acceseze interpretul Python în mediul nostru virtualenv, eventual pentru a rula scripturi de lucru. Așa că acum să marchem aceste două fișiere executabile și să verificăm dacă putem deschide pagina noastră de test Django.

chcon -t gunicorn_exec_t /srv/djangoapp/django/bin/gunicorn /srv/djangoapp/django/bin/python3.6

Serviciul gunicorn va trebui repornit înainte ca noua etichetă să poată fi selectată. Puteți să-l reporniți imediat sau să opriți serviciul și să lăsați socket-ul să-l pornească atunci când deschideți site-ul în browser. Verificați dacă procesele au primit etichetele corecte folosind ps.

ps -efZ | grep gunicorn

Nu uitați să creați o politică SELinux normală mai târziu!

Dacă te uiți la mesajele AVC acum, ultimul mesaj conține permissive=1 pentru tot ceea ce are legătură cu aplicația și permissive=0 pentru restul sistemului. Dacă înțelegeți de ce are nevoie de acces o aplicație reală, puteți găsi rapid cea mai bună modalitate de a rezolva astfel de probleme. Dar până atunci, cel mai bine este să păstrați sistemul în siguranță și să obțineți un audit clar și utilizabil al proiectului Django.

sudo ausearch -m AVC

S-a întâmplat!

A apărut un proiect Django care funcționează cu un frontend bazat pe Nginx și Gunicorn WSGI. Am configurat Python 3 și PostgreSQL 10 din depozitele RHEL 8 Beta. Acum puteți avansa și crea (sau pur și simplu implementa) aplicații Django sau puteți explora alte instrumente disponibile în RHEL 8 Beta pentru a automatiza procesul de configurare, a îmbunătăți performanța sau chiar a containeriza această configurație.

Sursa: www.habr.com

Adauga un comentariu