Praktijk RHEL 8 Beta: Laten we werkende webapplicaties bouwen

RHEL 8 Beta biedt ontwikkelaars veel nieuwe mogelijkheden, waarvan de opsomming pagina's in beslag kan nemen. Echter, nieuwe dingen leren is altijd het beste in de praktijk. Daarom stellen we voor om een praktische sessie te doorlopen over het daadwerkelijke creƫren van applicatie-infrastructuur op basis van Red Hat Enterprise Linux 8 Beta.

Praktijk RHEL 8 Beta: Laten we werkende webapplicaties bouwen

We nemen Python als basis, een populaire programmeertaal onder ontwikkelaars, in combinatie met Django en PostgreSQL, een vrij gebruikelijke combinatie voor het bouwen van applicaties, en we configureren RHEL 8 Beta om met hen te werken. Daarna voegen we nog een paar (niet-geheime) ingrediƫnten toe.

De testomgeving zal veranderen, want het is interessant om de mogelijkheden van automatisering te verkennen, met containers te werken en om omgevingen met meerdere servers uit te proberen. Om met een nieuw project te beginnen, kun je beginnen met het handmatig maken van een eenvoudig prototype – zo kun je zien wat er precies moet gebeuren en hoe de interactie plaatsvindt, en dan overstappen op automatisering en complexere configuraties. Vandaag gaan we het hebben over het maken van zo'n prototype.

We beginnen met het opzetten van een RHEL 8 Beta VM-image. Je kunt een virtuele machine vanaf nul installeren, of het KVM-gastenimage gebruiken dat beschikbaar is met een abonnement op Beta. Bij het gebruik van het gastenimage moet je een virtuele CD configureren die metadata en gebruikersgegevens voor cloud-init bevat. Er hoeft niets bijzonders met de schijfstructuur of beschikbare pakketten te gebeuren, elke configuratie is goed.

Laten we het hele proces in detail bekijken.

Installatie van Django

Met de nieuwste versie van Django heb je een virtuele omgeving (virtualenv) nodig met Python 3.5 of een latere versie. In de opmerkingen bij Beta kun je zien dat Python 3.6 beschikbaar is; laten we controleren of dit echt zo is:

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

Red Hat gebruikt actief Python als systeemtool in RHEL. Waarom krijgen we dan dit resultaat?

Het punt is dat veel ontwikkelaars die Python gebruiken, nog steeds nadenken over de overstap van Python 2 naar Python 3, terwijl Python 3 al actief wordt ontwikkeld en er voortdurend nieuwe versies verschijnen. Om tegemoet te komen aan de behoefte aan stabiele systeemtools en tegelijkertijd gebruikers toegang te bieden tot verschillende nieuwe versies van Python, is de systeem-Python verhuisd naar een nieuw pakket waarin zowel Python 2.7 als 3.6 kan worden geĆÆnstalleerd. Meer gedetailleerde informatie over de wijzigingen en de redenen waarom dit is gedaan, is te vinden in de publicatie op de blog van Langdon White (Langdon White).

Dus om een werkende Python te krijgen, hoeft slechts twee pakketten te worden geĆÆnstalleerd, waarbij python3-pip als afhankelijkheid wordt meegetrokken.

sudo yum install python36 python3-virtualenv

Waarom zou je geen directe verwijzingen naar de module gebruiken, zoals Langdon voorstelt, en geen pip3 installeren? In het licht van de aanstaande automatisering is het bekend dat voor Ansible een geĆÆnstalleerde pip nodig is, omdat de pip-module geen virtuele omgevingen (virtualenvs) met een op maat gemaakte pip-uitvoerbare bestand ondersteunt.

Met een werkende python3-interpreter kan het installatieproces van Django worden voortgezet en kan een werkend systeem worden verkregen samen met onze andere componenten. Er zijn veel manieren van implementatie beschikbaar op het internet. Hier wordt een versie gepresenteerd, maar gebruikers kunnen hun eigen processen gebruiken.

De versies van PostgreSQL en Nginx die standaard beschikbaar zijn in RHEL 8 zullen we installeren met Yum.

sudo yum install nginx postgresql-server

Voor PostgreSQL is psycopg2 nodig, maar het moet alleen beschikbaar zijn in de virtualenv-omgeving, dus zullen we het samen met Django en Gunicorn installeren met pip3. Maar eerst moeten we virtualenv instellen.

Er is altijd veel discussie over de juiste keuze van de installatieplaats voor Django-projecten, maar als er twijfels zijn, kan altijd worden verwezen naar de Linux Filesystem Hierarchy Standard. In het bijzonder vermeldt de FHS dat /srv wordt gebruikt voor: "opslag van gegevens die specifiek zijn voor een bepaalde host - gegevens die door het systeem worden weergegeven, zoals gegevens en scripts van webservers, gegevens die op FTP-servers worden opgeslagen, en repositories van versiebeheersystemen (die in FHS-2.3 in 2004 zijn verschenen)".

Dit is precies ons geval, dus we plaatsen alles wat we nodig hebben in /srv, waarvan de eigenaar onze applicatie gebruiker (cloud-user) is.

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

De configuratie van PostgreSQL en Django is eenvoudig: we maken een database aan, creƫren een gebruiker en stellen de rechten in. Er is ƩƩn ding om te onthouden bij de initiƫle installatie van PostgreSQL - het script postgresql-setup, dat samen met het postgresql-server pakket wordt geleverd. Dit script helpt bij het uitvoeren van basisadministratietaken voor de databasecluster, zoals het initialiseren van de cluster of het updaten. Om een nieuwe PostgreSQL instantie op een RHEL-systeem in te stellen, moeten we de volgende opdracht uitvoeren:

sudo /usr/bin/postgresql-setup --initdb

Vervolgens kunnen we PostgreSQL starten met systemd, een database aanmaken en het project in Django instellen. Vergeet niet PostgreSQL opnieuw te starten na het aanbrengen van wijzigingen in het configuratiebestand voor clientauthenticatie (meestal pg_hba.conf) om de opslag van het wachtwoord voor de applicatie gebruiker in te stellen. Als je tegen andere problemen aanloopt, zorg ervoor dat de instellingen voor IPv4 en IPv6 in het pg_hba.conf bestand zijn aangepast.

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

In het bestand /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

In het bestand /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 }}',
   }
}

Na het configureren van het settings.py bestand in het project en het instellen van de databaseconfiguratie, kan de ontwikkelingsserver worden gestart om te controleren of alles werkt. Na het starten van de ontwikkelingsserver is het goed om een admin gebruiker aan te maken om de verbinding met de database te testen.

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

WSGI? Wat is dat?

De ontwikkelingsserver is nuttig voor testen, maar om de applicatie uit te voeren is het noodzakelijk om de juiste server en proxy in te stellen voor de Web Server Gateway Interface (WSGI). Er zijn verschillende veelvoorkomende combinaties, zoals Apache HTTPD met uWSGI of Nginx met Gunicorn.

De taak van de Web Server Gateway Interface is om verzoeken te routeren van webserver aan het Python webframework. WSGI is een soort erfgoed van een afschuwelijk verleden, toen CGI-mechanismen gebruikelijk waren, en vandaag de dag is WSGI feitelijk de standaard, ongeacht de gebruikte webserver of Python-framework. Maar ondanks de brede verspreiding zijn er nog steeds veel nuances bij het werken met deze frameworks, en tal van keuzemogelijkheden. In dit geval zullen we proberen de interactie tussen Gunicorn en Nginx via een socket op te zetten.

Aangezien beide componenten op dezelfde server zijn geĆÆnstalleerd, zullen we proberen UNIX-socket in plaats van een netwerk-socket te gebruiken. Omdat we voor communicatie hoe dan ook een socket nodig hebben, proberen we nog een stap verder te gaan en de socketactivatie voor Gunicorn via systemd in te stellen.

Het proces om sockets geactiveerde services (socket activated services) te creƫren is vrij eenvoudig. Eerst wordt er een unit-bestand aangemaakt dat de directive ListenStream bevat, waarmee het punt wordt aangegeven waar de UNIX-socket zal worden gemaakt, en vervolgens een unit-bestand voor de service, waarin de directive Requires naar het socket-unit-bestand verwijst. Vervolgens hoeft er in het service-unit-bestand alleen nog maar Gunicorn uit de virtuele omgeving te worden aangeroepen en de WSGI-binding voor de UNIX-socket en de Django-app te creƫren.

Hier zijn enkele voorbeeld unit-bestanden die als basis kunnen dienen. Eerst configureren we de socket.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Nu moeten we de Gunicorn-daemon configureren.

[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

Voor Nginx is het voldoende om configuratiebestanden voor de proxy aan te maken en de directory voor statische inhoud in te stellen, als je deze gebruikt. In RHEL bevinden de configuratiebestanden van Nginx zich in /etc/nginx/conf.d. Je kunt het volgende voorbeeld kopiƫren naar het bestand /etc/nginx/conf.d/default.conf en de service starten. Zorg ervoor dat je de server_name hebt ingesteld volgens de naam van jouw host.

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

Start de Gunicorn-socket en Nginx met behulp van systemd, en je kunt beginnen met testen.

Bad Gateway-fout?

Als u een adres in de browser invoert, ontvangt u waarschijnlijk een 502 Bad Gateway-fout. Deze kan worden veroorzaakt door onjuiste machtigingen voor de UNIX-socket, of door complexere problemen met toegangbeheer in SELinux.

In het foutlog van nginx zal een regel van deze aard te vinden zijn:

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"

Als we Gunicorn rechtstreeks testen, krijgen we een lege reactie.

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

Laten we uitzoeken waarom dit gebeurt. Als we het logboek openen, zullen we waarschijnlijk zien dat het probleem gerelateerd is aan SELinux. Aangezien we een daemon hebben draaien voor welke geen eigen beleid is ingesteld, wordt deze gemarkeerd als init_t. Laten we deze theorie in de praktijk testen.

sudo setenforce 0

Dit kan tot kritiek en bloedige tranen leiden, maar het is gewoon het debuggen van een prototype. We schakelen de controle uit om te bevestigen dat het probleem hier daadwerkelijk ligt, waarna we alles weer terugzetten naar de oorspronkelijke staat.

Door de pagina in de browser te vernieuwen of onze curl-opdracht opnieuw uit te voeren, kunnen we de testpagina van Django bekijken.

Dus, nadat we ervan overtuigd zijn dat alles werkt en er geen verdere machtigingsproblemen zijn, zetten we SELinux opnieuw aan.

sudo setenforce 1

Hier zal geen uitleg worden gegeven over audit2allow en het creƫren van beleid op basis van waarschuwingen met sepolgen, omdat er op dit moment geen echte Django-applicatie is, en dus ook geen volledige kaart van waarop Gunicorn mogelijk toegang wil hebben, en waarop deze toegang moet worden verboden. Daarom is het noodzakelijk om de werking van SELinux te waarborgen ter bescherming van het systeem, terwijl we tegelijkertijd de applicatie toestaan om te draaien en logberichten te genereren, zodat we later een echt beleid op basis hiervan kunnen opstellen.

Toewijzen van toegestane domeinen (permissive domains)

Niet iedereen heeft gehoord van toegestane domeinen in SELinux, maar ze zijn niet nieuw. Veel mensen hebben er zelfs mee gewerkt, zonder het zelf te beseffen. Wanneer een beleid wordt gemaakt op basis van auditberichten, is het gegenereerde beleid een toegestaan domein. Laten we proberen een eenvoudige permissieve politiek te creƫren.

Om een specifiek toegestaan domein voor Gunicorn te creƫren, is een beleid nodig en moeten de bijbehorende bestanden gemarkeerd worden. Daarnaast zijn er tools nodig om nieuwe beleidsregels te verzamelen.

sudo yum install selinux-policy-devel

Het mechanisme van toegestane domeinen is een uitstekend hulpmiddel voor het identificeren van problemen, vooral als het gaat om een aangepast applicatie of applicaties die zonder reeds gemaakte beleidsregels worden geleverd. In dit geval zal het beleid voor het toegestane domein voor Gunicorn zo eenvoudig mogelijk zijn: we verklaren het hoofdtype (gunicorn_t), we verklaren het type dat we zullen gebruiken voor het markeren van verschillende uitvoerbare bestanden (gunicorn_exec_t), en vervolgens configureren we de overgang (transition) voor system, zodat we de draaiende processen correct kunnen markeren. De laatste regel stelt het beleid in als standaard toegestaan bij het laden.

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;

Dit beleidsbestand kan worden gecompileerd en aan het systeem worden toegevoegd.

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

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

Laten we controleren of SELinux nog iets blokkeert, naast hetgeen waar onze onbekende daemon toegang toe heeft.

sudo ausearch -m AVC

type=AVC msg=audit(1545315977.237:1273): avc: denied { write } voor 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 verhindert dat Nginx gegevens schrijft naar de UNIX-socket die door Gunicorn wordt gebruikt. Gewoonlijk begint men in zulke gevallen het beleid te wijzigen, maar er staan ook andere taken op de agenda. We kunnen ook de domeininstellingen wijzigen, waardoor het verandert van een beperkingsdomein naar een toestemmingsdomein. Laten we nu httpd_t naar het toestemmingsdomein verplaatsen. Dit zal Nginx de benodigde toegang geven, zodat we verder kunnen gaan met het debuggingproces.

sudo semanage permissive -a httpd_t

Dus, wanneer het is gelukt om de bescherming van SELinux te behouden (je moet een project nooit in de beperkingsmodus van SELinux achterlaten) en de toestemmingsdomeinen zijn geladen, moeten we begrijpen wat precies gemarkeerd moet worden als gunicorn_exec_t, zodat alles weer normaal werkt. Laten we de website bezoeken om te zien of er nieuwe berichten over toegang beperkingen verschijnen.

sudo ausearch -m AVC -c gunicorn

Je kunt veel berichten zien die 'comm="gunicorn"' bevatten, die verschillende acties uitvoeren op bestanden in /srv/djangoapp, dus het is duidelijk dat dit een van de commando's is die we moeten markeren.

Daarnaast verschijnt er een bericht van dit type:

type=AVC msg=audit(1545320700.070:1542): avc: denied { execute } voor pid=20704 comm="(gunicorn)" naam="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

Als we de status van de gunicorn-service bekijken of het commando ps uitvoeren, verschijnen er geen actieve processen. Het lijkt erop dat gunicorn probeert toegang te krijgen tot de Python-interpreter in onze virtualenv-omgeving, mogelijk om werkprocessen (workers) te starten. Laten we daarom deze twee uitvoerbare bestanden markeren en controleren of we onze testpagina van Django kunnen openen.

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

Het is nodig om de gunicorn-service opnieuw op te starten, zodat de nieuwe label kan worden gekozen. Je kunt het meteen opnieuw opstarten of de service stoppen en het socket zijn werk laten doen bij het openen van de site in je browser. Zorg ervoor dat de processen de juiste labels hebben gekregen door ps te gebruiken.

ps -efZ | grep gunicorn

Vergeet niet om daarna een goede SELinux-policy aan te maken!

Als we de AVC-berichten nu bekijken, bevat het laatste bericht permissive=1 voor alles wat met de applicatie te maken heeft, en permissive=0 voor de rest van het systeem. Als je begrijpt welke specifieke toegang de echte applicatie nodig heeft, kun je sneller de optimale oplossing voor dergelijke problemen vinden. Maar tot die tijd is het beter dat het systeem beveiligd blijft, om een begrijpelijke en bruikbare audit van het Django-project te krijgen.

sudo ausearch -m AVC

Gelukt!

Er is een werkend Django-project met een frontend op Nginx en Gunicorn WSGI. We hebben Python 3 en PostgreSQL 10 uit de RHEL 8 Beta-repositories ingesteld. Nu kunnen we verder gaan en Django-applicaties maken (of gewoon implementeren) of andere beschikbare tools in RHEL 8 Beta verkennen voor het automatiseren van het configuratieproces, het verbeteren van de prestaties of zelfs het containeriseren van deze configuratie.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers šŸ”„ Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster