Warsztaty RHEL 8 Beta: Tworzenie działających aplikacji internetowych

RHEL 8 Beta oferuje programistom wiele nowych funkcji, których wyliczanie mogłoby zająć całe strony, jednak nauka nowych rzeczy zawsze jest lepsza w praktyce, dlatego poniżej oferujemy warsztaty dotyczące faktycznego tworzenia infrastruktury aplikacji w oparciu o Red Hat Enterprise Linux 8 Beta.

Warsztaty RHEL 8 Beta: Tworzenie działających aplikacji internetowych

Weźmy za podstawę Python, popularny język programowania wśród programistów, kombinację Django i PostgreSQL, dość powszechną kombinację do tworzenia aplikacji, i skonfiguruj RHEL 8 Beta do pracy z nimi. Następnie dodamy jeszcze kilka (niesklasyfikowanych) składników.

Środowisko testowe ulegnie zmianie, ponieważ interesujące jest odkrywanie możliwości automatyzacji, pracy z kontenerami i wypróbowywania środowisk z wieloma serwerami. Aby rozpocząć nowy projekt, możesz zacząć od ręcznego stworzenia małego, prostego prototypu, dzięki czemu będziesz mógł dokładnie zobaczyć, co musi się wydarzyć i jak to współdziała, a następnie przejść do automatyzacji i tworzenia bardziej złożonych konfiguracji. Dziś mówimy o stworzeniu takiego prototypu.

Zacznijmy od wdrożenia obrazu maszyny wirtualnej RHEL 8 Beta. Możesz zainstalować maszynę wirtualną od podstaw lub skorzystać z obrazu gościa KVM dostępnego w ramach subskrypcji Beta. W przypadku korzystania z obrazu gościa konieczne będzie skonfigurowanie wirtualnej płyty CD zawierającej metadane i dane użytkownika na potrzeby inicjalizacji w chmurze (cloud-init). Nie musisz robić nic specjalnego ze strukturą dysku ani dostępnymi pakietami; wystarczy dowolna konfiguracja.

Przyjrzyjmy się bliżej całemu procesowi.

Instalowanie Django

Do najnowszej wersji Django potrzebne będzie środowisko wirtualne (virtualenv) z Pythonem 3.5 lub nowszym. W notatkach Beta widać, że dostępny jest Python 3.6, sprawdźmy, czy rzeczywiście tak jest:

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

Red Hat aktywnie używa Pythona jako zestawu narzędzi systemowych w RHEL, więc dlaczego tak się dzieje?

Faktem jest, że wielu programistów Pythona wciąż rozważa przejście z Pythona 2 na Pythona 2, podczas gdy sam Python 3 jest w fazie aktywnego rozwoju i stale pojawia się coraz więcej nowych wersji. Dlatego, aby zaspokoić zapotrzebowanie na stabilne narzędzia systemowe, oferując jednocześnie użytkownikom dostęp do różnych nowych wersji Pythona, systemowy Python został przeniesiony do nowego pakietu i zapewnił możliwość instalacji zarówno Pythona 2.7, jak i 3.6. Więcej informacji na temat zmian i powodów ich wprowadzenia można znaleźć w publikacji w Blog Langdona White'a (Langdona White’a).

Tak więc, aby uruchomić Pythona, wystarczy zainstalować dwa pakiety z python3-pip dołączonym jako zależność.

sudo yum install python36 python3-virtualenv

Dlaczego nie skorzystać z bezpośrednich wywołań modułów, jak sugeruje Langdon i zainstalować pip3? Mając na uwadze nadchodzącą automatyzację, wiadomo, że Ansible będzie wymagał zainstalowania pip do działania, ponieważ moduł pip nie obsługuje virtualenvs z niestandardowym plikiem wykonywalnym pip.

Mając do dyspozycji działający interpreter Pythona3, możesz kontynuować proces instalacji Django i mieć działający system wraz z innymi naszymi komponentami. W Internecie dostępnych jest wiele możliwości realizacji. Przedstawiona jest tutaj jedna wersja, ale użytkownicy mogą korzystać z własnych procesów.

Domyślnie zainstalujemy wersje PostgreSQL i Nginx dostępne w RHEL 8 przy użyciu Yum.

sudo yum install nginx postgresql-server

PostgreSQL będzie wymagał psycopg2, ale musi być dostępny tylko w środowisku virtualenv, więc zainstalujemy go za pomocą pip3 wraz z Django i Gunicorn. Ale najpierw musimy skonfigurować virtualenv.

Zawsze toczy się wiele dyskusji na temat wyboru odpowiedniego miejsca do instalacji projektów Django, ale w razie wątpliwości zawsze można zwrócić się do Linux Filesystem Hierarchy Standard. W szczególności FHS twierdzi, że /srv służy do: „przechowywania danych specyficznych dla hosta — danych wytwarzanych przez system, takich jak dane i skrypty serwera WWW, dane przechowywane na serwerach FTP i repozytoria systemu kontroli”. -2.3 w 2004 r.).”

Dokładnie tak jest w naszym przypadku, więc wszystko, czego potrzebujemy, umieszczamy w katalogu /srv, którego właścicielem jest użytkownik naszej aplikacji (użytkownik chmury).

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

Konfiguracja PostgreSQL i Django jest prosta: utwórz bazę danych, utwórz użytkownika, skonfiguruj uprawnienia. Jedną rzeczą, o której należy pamiętać podczas pierwszej instalacji PostgreSQL, jest skrypt postgresql-setup, który jest instalowany z pakietem postgresql-server. Skrypt ten ułatwia wykonywanie podstawowych zadań związanych z administrowaniem klastrem bazy danych, takich jak inicjowanie klastra lub proces aktualizacji. Aby skonfigurować nową instancję PostgreSQL w systemie RHEL, musimy uruchomić komendę:

sudo /usr/bin/postgresql-setup -initdb

Następnie możesz uruchomić PostgreSQL przy użyciu systemd, utworzyć bazę danych i skonfigurować projekt w Django. Pamiętaj, aby ponownie uruchomić PostgreSQL po wprowadzeniu zmian w pliku konfiguracyjnym uwierzytelniania klienta (zwykle pg_hba.conf), aby skonfigurować przechowywanie haseł dla użytkownika aplikacji. Jeśli napotkasz inne trudności, pamiętaj o zmianie ustawień IPv4 i IPv6 w pliku 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

W pliku /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

W pliku /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 }}',
   }
}

Po skonfigurowaniu pliku settings.py w projekcie i skonfigurowaniu konfiguracji bazy danych, możesz uruchomić serwer deweloperski, aby upewnić się, że wszystko działa. Po uruchomieniu serwera deweloperskiego warto utworzyć użytkownika admin w celu przetestowania połączenia z bazą danych.

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

WSGI? Czekaj?

Serwer deweloperski jest przydatny do testowania, ale aby uruchomić aplikację, należy skonfigurować odpowiedni serwer i serwer proxy dla interfejsu bramy serwera WWW (WSGI). Istnieje kilka typowych kombinacji, na przykład Apache HTTPD z uWSGI lub Nginx z Gunicorn.

Zadaniem interfejsu bramy serwera WWW jest przekazywanie żądań z serwera WWW do środowiska sieciowego Python. WSGI jest reliktem straszliwej przeszłości, kiedy istniały silniki CGI, a dziś WSGI jest de facto standardem, niezależnie od używanego serwera WWW czy frameworku Python. Jednak pomimo szerokiego zastosowania, praca z tymi frameworkami wciąż wiąże się z wieloma niuansami i wieloma możliwościami wyboru. W tym przypadku postaramy się nawiązać interakcję pomiędzy Gunicornem i Nginxem za pośrednictwem gniazda.

Ponieważ oba te komponenty są zainstalowane na tym samym serwerze, spróbujmy użyć gniazda UNIX zamiast gniazda sieciowego. Ponieważ komunikacja i tak wymaga gniazda, spróbujmy zrobić jeszcze jeden krok i skonfigurować aktywację gniazda dla Gunicorn poprzez systemd.

Proces tworzenia usług aktywowanych przez gniazdo jest dość prosty. Najpierw tworzony jest plik jednostkowy zawierający dyrektywę ListenStream wskazującą punkt, w którym zostanie utworzone gniazdo UNIX, a następnie plik jednostkowy dla usługi, w której dyrektywa Requires będzie wskazywała plik jednostkowy gniazda. Następnie w pliku jednostki usługowej pozostaje już tylko wywołać Gunicorn ze środowiska wirtualnego i utworzyć powiązanie WSGI dla gniazda UNIX i aplikacji Django.

Oto kilka przykładów plików jednostkowych, które można wykorzystać jako podstawę. Najpierw ustawiliśmy gniazdo.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Teraz musisz skonfigurować demona 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

W przypadku Nginx jest to prosta kwestia utworzenia plików konfiguracyjnych proxy i skonfigurowania katalogu do przechowywania zawartości statycznej, jeśli go używasz. W RHEL pliki konfiguracyjne Nginx znajdują się w /etc/nginx/conf.d. Możesz skopiować poniższy przykład do pliku /etc/nginx/conf.d/default.conf i uruchomić usługę. Upewnij się, że nazwa_serwera jest zgodna z nazwą hosta.

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

Uruchom gniazdo Gunicorn i Nginx za pomocą systemd i możesz rozpocząć testowanie.

Zły błąd bramy?

Jeśli wpiszesz adres w przeglądarce, najprawdopodobniej pojawi się błąd 502 Bad Gateway. Może to być spowodowane niepoprawnie skonfigurowanymi uprawnieniami gniazd UNIX, ale może też wynikać z bardziej złożonych problemów związanych z kontrolą dostępu w SELinux.

W dzienniku błędów nginx możesz zobaczyć taką linię:

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"

Jeśli bezpośrednio przetestujemy Gunicorn, otrzymamy pustą odpowiedź.

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

Zastanówmy się, dlaczego tak się dzieje. Jeśli otworzysz dziennik, najprawdopodobniej zobaczysz, że problem jest związany z SELinux. Ponieważ uruchamiamy demona, dla którego nie utworzono żadnej polityki, jest on oznaczony jako init_t. Przetestujmy tę teorię w praktyce.

sudo setenforce 0

Wszystko to może wywołać krytykę i krwawe łzy, ale to tylko debugowanie prototypu. Wyłączmy sprawdzanie, aby upewnić się, że to jest problem, po czym przywrócimy wszystko na swoje miejsce.

Odświeżając stronę w przeglądarce lub ponownie uruchamiając polecenie curl, możesz wyświetlić stronę testową Django.

Zatem upewniwszy się, że wszystko działa i nie ma już problemów z uprawnieniami, ponownie włączamy SELinux.

sudo setenforce 1

Nie będę tutaj mówił o audyt2allow ani o tworzeniu polityk opartych na alertach za pomocą sepolgen, ponieważ w tej chwili nie ma żadnej aplikacji Django, więc nie ma pełnej mapy tego, do czego Gunicorn mógłby chcieć uzyskać dostęp, a do czego powinien odmówić dostępu. Dlatego konieczne jest pozostawienie działającego SELinuksa, aby chronić system, a jednocześnie pozwolić aplikacji na działanie i pozostawienie komunikatów w dzienniku audytu, aby można było z nich następnie utworzyć rzeczywistą politykę.

Określanie domen zezwalających

Nie każdy słyszał o dozwolonych domenach w SELinuxie, ale nie są one niczym nowym. Wielu nawet z nimi współpracowało, nawet nie zdając sobie z tego sprawy. Gdy zasada jest tworzona na podstawie komunikatów kontrolnych, utworzona zasada reprezentuje rozwiązaną domenę. Spróbujmy stworzyć prostą politykę zezwoleń.

Aby utworzyć konkretną dozwoloną domenę dla Gunicorn, potrzebujesz jakiejś polityki, a także musisz zaznaczyć odpowiednie pliki. Ponadto potrzebne są narzędzia do tworzenia nowych polityk.

sudo yum install selinux-policy-devel

Mechanizm dozwolonych domen jest doskonałym narzędziem do identyfikowania problemów, zwłaszcza jeśli chodzi o niestandardową aplikację lub aplikacje dostarczane bez utworzonych już polityk. W tym przypadku dozwolona polityka domeny dla Gunicorn będzie tak prosta, jak to tylko możliwe - zadeklaruj typ główny (gunicorn_t), zadeklaruj typ, którego będziemy używać do oznaczania wielu plików wykonywalnych (gunicorn_exec_t), a następnie skonfiguruj przejście, aby system poprawnie oznaczył działające procesy. Ostatnia linia ustawia politykę jako domyślnie włączoną w momencie jej załadowania.

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;

Możesz skompilować ten plik zasad i dodać go do swojego systemu.

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

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

Sprawdźmy, czy SELinux blokuje coś innego niż to, do czego uzyskuje dostęp nasz nieznany demon.

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 uniemożliwia Nginxowi zapisywanie danych w gnieździe UNIX używanym przez Gunicorn. Zwykle w takich przypadkach polityka zaczyna się zmieniać, ale przed nami stoją inne wyzwania. Możesz także zmienić ustawienia domeny z domeny z ograniczeniami na domenę z uprawnieniami. Teraz przenieśmy httpd_t do domeny uprawnień. Zapewni to Nginxowi niezbędny dostęp i będziemy mogli kontynuować dalsze prace związane z debugowaniem.

sudo semanage permissive -a httpd_t

Tak więc, kiedy już uda Ci się chronić SELinux (naprawdę nie powinieneś zostawiać projektu SELinux w trybie ograniczonym) i załadowane zostaną domeny uprawnień, musisz dowiedzieć się, co dokładnie należy oznaczyć jako gunicorn_exec_t, aby wszystko działało poprawnie Ponownie. Spróbujmy wejść na stronę i zobaczyć nowe komunikaty o ograniczeniach dostępu.

sudo ausearch -m AVC -c gunicorn

Zobaczysz wiele komunikatów zawierających „comm="gunicorn"', które wykonują różne czynności na plikach w /srv/djangoapp, więc jest to oczywiście jedno z poleceń, które warto oznaczyć.

Ale dodatkowo pojawia się taki komunikat:

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

Jeśli spojrzysz na status usługi gunicorn lub uruchomisz polecenie ps, nie zobaczysz żadnych uruchomionych procesów. Wygląda na to, że gunicorn próbuje uzyskać dostęp do interpretera Pythona w naszym środowisku virtualenv, prawdopodobnie w celu uruchomienia skryptów roboczych. Zatem teraz zaznaczmy te dwa pliki wykonywalne i sprawdźmy, czy możemy otworzyć naszą stronę testową Django.

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

Zanim będzie można wybrać nowy tag, konieczne będzie ponowne uruchomienie usługi gunicorn. Możesz natychmiast uruchomić ją ponownie lub zatrzymać usługę i pozwolić, aby gniazdo uruchomiło ją po otwarciu witryny w przeglądarce. Sprawdź, czy procesy otrzymały prawidłowe etykiety, używając ps.

ps -efZ | grep gunicorn

Nie zapomnij później utworzyć normalnej polityki SELinux!

Jeśli teraz spojrzysz na komunikaty AVC, ostatni komunikat zawiera permissive=1 dla wszystkiego, co jest związane z aplikacją i permissive=0 dla reszty systemu. Jeśli zrozumiesz, jakiego rodzaju dostępu potrzebuje prawdziwa aplikacja, możesz szybko znaleźć najlepszy sposób rozwiązania takich problemów. Ale do tego czasu najlepiej jest zapewnić bezpieczeństwo systemu i uzyskać przejrzysty, użyteczny audyt projektu Django.

sudo ausearch -m AVC

Stało się!

Pojawił się działający projekt Django z frontendem opartym na Nginx i Gunicorn WSGI. Skonfigurowaliśmy Python 3 i PostgreSQL 10 z repozytoriów RHEL 8 Beta. Teraz możesz iść dalej i tworzyć (lub po prostu wdrażać) aplikacje Django lub eksplorować inne narzędzia dostępne w RHEL 8 Beta, aby zautomatyzować proces konfiguracji, poprawić wydajność, a nawet konteneryzować tę konfigurację.

Źródło: www.habr.com

Dodaj komentarz