Workshop RHEL 8 Beta: Werkende webapplicaties bouwen

RHEL 8 Beta biedt ontwikkelaars veel nieuwe functies, waarvan de lijst pagina's kan beslaan, maar nieuwe dingen leren is in de praktijk altijd beter, dus hieronder bieden we een workshop aan over het daadwerkelijk creëren van een applicatie-infrastructuur op basis van Red Hat Enterprise Linux 8 Beta.

Workshop RHEL 8 Beta: Werkende webapplicaties bouwen

Laten we Python, een populaire programmeertaal onder ontwikkelaars, als basis nemen, een combinatie van Django en PostgreSQL, een vrij gebruikelijke combinatie voor het maken van applicaties, en RHEL 8 Beta configureren om ermee te werken. Dan voegen we nog een paar (niet-geclassificeerde) ingrediënten toe.

De testomgeving zal veranderen, omdat het interessant is om de mogelijkheden van automatisering, het werken met containers en het uitproberen van omgevingen met meerdere servers te verkennen. Om aan de slag te gaan met een nieuw project, kunt u beginnen met het met de hand maken van een klein, eenvoudig prototype, zodat u precies kunt zien wat er moet gebeuren en hoe dit op elkaar inwerkt. Vervolgens kunt u doorgaan met het automatiseren en creëren van complexere configuraties. Vandaag hebben we het over de creatie van een dergelijk prototype.

Laten we beginnen met het implementeren van de RHEL 8 Beta VM-image. U kunt een geheel nieuwe virtuele machine installeren of de KVM-gastimage gebruiken die beschikbaar is bij uw bèta-abonnement. Wanneer u een gastimage gebruikt, moet u een virtuele CD configureren die metagegevens en gebruikersgegevens bevat voor cloudinitialisatie (cloud-init). U hoeft niets speciaals te doen met de schijfstructuur of beschikbare pakketten; elke configuratie is voldoende.

Laten we het hele proces eens nader bekijken.

Django installeren

Met de nieuwste versie van Django heb je een virtuele omgeving (virtualenv) nodig met Python 3.5 of hoger. In de Beta-opmerkingen kun je zien dat Python 3.6 beschikbaar is, laten we eens kijken of dit inderdaad het geval is:

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

Red Hat gebruikt Python actief als systeemtoolkit in RHEL, dus waarom is dit het resultaat?

Feit is dat veel Python-ontwikkelaars nog steeds nadenken over de overgang van Python 2 naar Python 2, terwijl Python 3 zelf actief wordt ontwikkeld en er voortdurend meer en meer 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, werd systeem Python daarom naar een nieuw pakket verplaatst en werd de mogelijkheid geboden om zowel Python 2.7 als 3.6 te installeren. Meer informatie over de wijzigingen en waarom deze zijn doorgevoerd, vindt u in de publicatie in Langdon White's blog (Langdon Wit).

Om Python aan de slag te krijgen, hoeft u dus slechts twee pakketten te installeren, waarbij python3-pip als afhankelijkheid is opgenomen.

sudo yum install python36 python3-virtualenv

Waarom zouden we geen directe moduleaanroepen gebruiken zoals Langdon voorstelt en pip3 installeren? Rekening houdend met de komende automatisering is het bekend dat Ansible pip moet installeren om te kunnen werken, omdat de pip-module geen virtualenvs ondersteunt met een aangepast pip-uitvoerbaar bestand.

Met een werkende Python3-tolk tot uw beschikking kunt u doorgaan met het Django-installatieproces en beschikt u over een werkend systeem samen met onze andere componenten. Er zijn veel implementatiemogelijkheden beschikbaar op internet. Er wordt hier één versie gepresenteerd, maar gebruikers kunnen hun eigen processen gebruiken.

We zullen de PostgreSQL- en Nginx-versies die beschikbaar zijn in RHEL 8 standaard installeren met Yum.

sudo yum install nginx postgresql-server

PostgreSQL heeft psycopg2 nodig, maar het hoeft alleen beschikbaar te zijn in een virtualenv-omgeving, dus we zullen het installeren met pip3 samen met Django en Gunicorn. Maar eerst moeten we virtualenv instellen.

Er is altijd veel discussie over het kiezen van de juiste plaats om Django-projecten te installeren, maar bij twijfel kun je altijd terecht bij de Linux Filesystem Hierarchy Standard. Concreet zegt de FHS dat /srv wordt gebruikt om: “hostspecifieke gegevens op te slaan – gegevens die het systeem produceert, zoals webservergegevens en scripts, gegevens die zijn opgeslagen op FTP-servers en repository’s van het controlesysteem.” -2.3 in 2004).

Dit is precies ons geval, dus we stoppen alles wat we nodig hebben in /srv, dat eigendom is van onze applicatiegebruiker (cloud-gebruiker).

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

Het instellen van PostgreSQL en Django is eenvoudig: maak een database aan, maak een gebruiker aan, configureer rechten. Eén ding om in gedachten te houden bij de eerste installatie van PostgreSQL is het postgresql-setup-script dat wordt geïnstalleerd met het postgresql-server-pakket. Dit script helpt u bij het uitvoeren van basistaken die verband houden met het beheer van databaseclusters, zoals clusterinitialisatie of het upgradeproces. Om een ​​nieuwe PostgreSQL-instantie op een RHEL-systeem te configureren, moeten we de opdracht uitvoeren:

sudo /usr/bin/postgresql-setup -initdb

Vervolgens kunt u PostgreSQL starten met systemd, een database maken en een project opzetten in Django. Vergeet niet om PostgreSQL opnieuw te starten nadat u wijzigingen hebt aangebracht in het configuratiebestand voor clientauthenticatie (meestal pg_hba.conf) om de wachtwoordopslag voor de toepassingsgebruiker te configureren. Als u andere problemen ondervindt, zorg er dan voor dat u de IPv4- en IPv6-instellingen in het bestand pg_hba.conf wijzigt.

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 }}',
   }
}

Nadat u het bestand settings.py in het project hebt geconfigureerd en de databaseconfiguratie hebt ingesteld, kunt u de ontwikkelingsserver starten om te controleren of alles werkt. Na het starten van de ontwikkelserver is het een goed idee 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? Wai?

De ontwikkelingsserver is handig voor het testen, maar om de applicatie uit te voeren moet u de juiste server en proxy voor de Web Server Gateway Interface (WSGI) configureren. Er zijn verschillende veel voorkomende combinaties, bijvoorbeeld Apache HTTPD met uWSGI of Nginx met Gunicorn.

De taak van de Web Server Gateway Interface is het doorsturen van verzoeken van de webserver naar het Python-webframework. WSGI is een overblijfsel uit het verschrikkelijke verleden toen er CGI-engines bestonden, en vandaag de dag is WSGI de de facto standaard, ongeacht de gebruikte webserver of het gebruikte Python-framework. Maar ondanks het wijdverbreide gebruik ervan zijn er nog steeds veel nuances bij het werken met deze raamwerken, en veel keuzes. In dit geval zullen we proberen interactie tussen Gunicorn en Nginx tot stand te brengen via een socket.

Omdat beide componenten op dezelfde server zijn geïnstalleerd, proberen we een UNIX-socket te gebruiken in plaats van een netwerksocket. Omdat communicatie in ieder geval een socket vereist, laten we proberen nog een stap te zetten en socketactivering voor Gunicorn te configureren via systemd.

Het proces voor het maken van socket-geactiveerde services is vrij eenvoudig. Eerst wordt een eenheidsbestand gemaakt dat een ListenStream-instructie bevat die verwijst naar het punt waarop de UNIX-socket zal worden gemaakt, en vervolgens een eenheidsbestand voor de service waarin de Requires-instructie naar het socket-eenheidsbestand zal verwijzen. Vervolgens hoeft u in het service unit-bestand Gunicorn alleen nog maar aan te roepen vanuit de virtuele omgeving en een WSGI-binding te maken voor de UNIX-socket en de Django-applicatie.

Hier vindt u enkele voorbeelden van eenheidsbestanden die u als basis kunt gebruiken. Eerst hebben we de socket geïnstalleerd.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Nu moet je 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 eenvoudigweg een kwestie van proxyconfiguratiebestanden maken en een map instellen om statische inhoud op te slaan als u er een gebruikt. In RHEL bevinden de Nginx-configuratiebestanden zich in /etc/nginx/conf.d. U kunt het volgende voorbeeld naar het bestand /etc/nginx/conf.d/default.conf kopiëren en de service starten. Zorg ervoor dat u de servernaam zo instelt dat deze overeenkomt met uw hostnaam.

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 systemd en je bent klaar om te beginnen met testen.

Slechte gatewayfout?

Als u het adres in uw browser invoert, ontvangt u hoogstwaarschijnlijk een 502 Bad Gateway-foutmelding. Het kan veroorzaakt worden door onjuist geconfigureerde UNIX-socket-permissies, of het kan te wijten zijn aan meer complexe problemen gerelateerd aan toegangscontrole in SELinux.

In het nginx-foutenlogboek zie je een regel als deze:

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 leeg antwoord.

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

Laten we uitzoeken waarom dit gebeurt. Als je het log opent, zul je hoogstwaarschijnlijk zien dat het probleem verband houdt met SELinux. Omdat we een daemon uitvoeren waarvoor geen beleid is gemaakt, wordt deze gemarkeerd als init_t. Laten we deze theorie in de praktijk testen.

sudo setenforce 0

Dit alles kan kritiek en bloedtranen veroorzaken, maar dit is slechts het debuggen van het prototype. Laten we de controle uitschakelen om er zeker van te zijn dat dit het probleem is, waarna we alles terug op zijn plaats zetten.

Door de pagina in de browser te vernieuwen of onze curl-opdracht opnieuw uit te voeren, kunt u de Django-testpagina zien.

Dus nadat we er zeker van zijn dat alles werkt en er geen toestemmingsproblemen meer zijn, schakelen we SELinux opnieuw in.

sudo setenforce 1

Ik zal het hier niet hebben over audit2allow of het creëren van op waarschuwingen gebaseerd beleid met sepolgen, aangezien er op dit moment geen echte Django-applicatie is, dus er is geen volledige kaart van waar Gunicorn toegang toe zou willen hebben en waartoe het de toegang zou moeten weigeren. Daarom is het nodig om SELinux draaiende te houden om het systeem te beschermen, terwijl je tegelijkertijd de applicatie laat draaien en berichten achterlaat in de auditlog zodat het daadwerkelijke beleid er vervolgens van gemaakt kan worden.

Permissieve domeinen opgeven

Niet iedereen heeft gehoord van toegestane domeinen in SELinux, maar ze zijn niets nieuws. Velen werkten zelfs met hen samen zonder het zelfs maar te beseffen. Wanneer een beleid wordt gemaakt op basis van auditberichten, vertegenwoordigt het gemaakte beleid het opgeloste domein. Laten we proberen een eenvoudig vergunningenbeleid te creëren.

Om een ​​specifiek toegestaan ​​domein voor Gunicorn te creëren, heb je een soort beleid nodig, en moet je ook de juiste bestanden markeren. Daarnaast zijn er instrumenten nodig om nieuw beleid te kunnen ontwikkelen.

sudo yum install selinux-policy-devel

Het mechanisme voor toegestane domeinen is een geweldig hulpmiddel voor het identificeren van problemen, vooral als het gaat om een ​​aangepaste applicatie of applicaties die worden geleverd zonder dat er al beleid is gemaakt. In dit geval zal het toegestane domeinbeleid voor Gunicorn zo eenvoudig mogelijk zijn: declareer een hoofdtype (gunicorn_t), declareer een type dat we zullen gebruiken om meerdere uitvoerbare bestanden te markeren (gunicorn_exec_t), en stel vervolgens een overgang in zodat het systeem correct kan markeren lopende processen. Op de laatste regel wordt het beleid standaard ingesteld op het moment dat het wordt geladen.

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;

U kunt dit beleidsbestand samenstellen en aan uw systeem toevoegen.

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 iets anders blokkeert dan waartoe onze onbekende daemon toegang heeft.

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 voorkomt dat Nginx gegevens schrijft naar de UNIX-socket die door Gunicorn wordt gebruikt. Normaal gesproken begint in dergelijke gevallen het beleid te veranderen, maar er liggen nog andere uitdagingen in het verschiet. U kunt ook de domeininstellingen wijzigen van een beperkingsdomein naar een toestemmingsdomein. Laten we nu httpd_t naar het permissiedomein verplaatsen. Dit geeft Nginx de nodige toegang en we kunnen doorgaan met verder foutopsporingswerk.

sudo semanage permissive -a httpd_t

Dus als het je eenmaal is gelukt om SELinux beschermd te houden (je zou een SELinux-project echt niet in de beperkte modus moeten laten) en de toestemmingsdomeinen zijn geladen, moet je uitzoeken wat er precies moet worden gemarkeerd als gunicorn_exec_t om alles goed te laten werken opnieuw. Laten we proberen de website te bezoeken om nieuwe berichten over toegangsbeperkingen te zien.

sudo ausearch -m AVC -c gunicorn

Je zult veel berichten zien die 'comm="gunicorn"' bevatten die verschillende dingen doen met bestanden in /srv/djangoapp, dus dit is duidelijk een van de commando's die het waard zijn om te markeren.

Maar daarnaast verschijnt er een bericht als dit:

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

Als u de status van de gunicorn-service bekijkt of de opdracht ps uitvoert, ziet u geen actieve processen. Het lijkt erop dat gunicorn toegang probeert te krijgen tot de Python-interpreter in onze virtualenv-omgeving, mogelijk om werkscripts uit te voeren. Laten we nu deze twee uitvoerbare bestanden markeren en controleren of we onze Django-testpagina kunnen openen.

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

De gunicorn-service moet opnieuw worden gestart voordat de nieuwe tag kan worden geselecteerd. U kunt hem onmiddellijk opnieuw opstarten of de service stopzetten en de socket deze laten starten wanneer u de site in de browser opent. Controleer of processen de juiste labels hebben gekregen met behulp van ps.

ps -efZ | grep gunicorn

Vergeet niet later een normaal SELinux-beleid aan te maken!

Als je nu naar de AVC-berichten kijkt, 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 u begrijpt wat voor soort toegang een echte applicatie nodig heeft, kunt u snel de beste manier vinden om dergelijke problemen op te lossen. Maar tot die tijd is het het beste om het systeem veilig te houden en een duidelijke, bruikbare audit van het Django-project te krijgen.

sudo ausearch -m AVC

Gebeurd!

Er is een werkend Django-project verschenen met een frontend gebaseerd op Nginx en Gunicorn WSGI. We hebben Python 3 en PostgreSQL 10 geconfigureerd vanuit de RHEL 8 Beta-repository's. Nu kunt u verder gaan en Django-applicaties maken (of eenvoudigweg implementeren) of andere beschikbare tools in RHEL 8 Beta verkennen om het configuratieproces te automatiseren, de prestaties te verbeteren of deze configuratie zelfs in een container te plaatsen.

Bron: www.habr.com

Voeg een reactie