Praktikum RHEL 8 Beta: Vi bygger fungerende webapplikationer

RHEL 8 Beta tilbyder udviklere mange nye muligheder, hvis liste kunne tage sider, men det er altid bedre at lære det nye i praksis. Derfor foreslår vi nedenfor at gennemgå en praktisk øvelse i oprettelsen af en applikationsinfrastruktur baseret på Red Hat Enterprise Linux 8 Beta.

Praktikum RHEL 8 Beta: Vi bygger fungerende webapplikationer

Vi tager udgangspunkt i Python, et populært programmeringssprog blandt udviklere, samt en kombination af Django og PostgreSQL, en ret almindelig kombination til oprettelse af applikationer, og konfigurerer RHEL 8 Beta til at arbejde med dem. Derefter tilføjer vi et par (ikke-hemmelige) ingredienser.

Testmiljøet vil ændre sig, da det er spændende at undersøge automatiseringsmuligheder, arbejde med containere og prøve miljøer med flere servere. For at komme i gang med det nye projekt kan man starte med at oprette en lille simpel prototype manuelt – på den måde kan man se, hvad der egentligt skal ske, og hvordan interaktionen finder sted, og derefter gå videre til automatisering og oprettelse af mere komplekse konfigurationer. I dag handler det om at skabe en sådan prototype.

Lad os starte med at implementere billedet af den virtuelle maskine RHEL 8 Beta VM. Man kan installere den virtuelle maskine fra bunden, eller bruge en gæsteimage KVM, der er tilgængelig med abonnementet på Beta. Når man bruger gæsteimage, skal man konfigurere en virtuel CD, som vil indeholde metadata og brugerdata til cloud-init. Der er ikke behov for at ændre noget ved diskstrukturen eller de tilgængelige pakker; enhver konfiguration vil være tilstrækkelig.

Lad os se nærmere på hele processen.

Installation af Django

Med den nyeste version af Django vil der være behov for et virtuelt miljø (virtualenv) med Python 3.5 eller nyere. I bemærkningerne til Beta kan man se, at Python 3.6 er tilgængelig, lad os tjekke, om det virkelig er tilfældet:

[cloud-user@8beta1 ~]$ python
-bash: python: kommando ikke fundet
[cloud-user@8beta1 ~]$ python3
-bash: python3: kommando ikke fundet

Red Hat bruger aktivt Python som systemværktøj i RHEL, så hvorfor får vi sådan et resultat?

Mange udviklere, der bruger Python, overvejer stadig at skifte fra Python 2 til Python 2, mens Python 3 aktivt udvikles, og der konstant kommer nye versioner. For at imødekomme behovet for stabile systemværktøjer og samtidig give brugerne adgang til forskellige nye versioner af Python, er system-Python blevet flyttet til en ny pakke, som gør det muligt at installere både Python 2.7 og 3.6. Du kan finde mere information om ændringerne og årsagerne til dette i publikation i Langdon Whites blog (Langdon White).

For at få fungerende Python skal du blot installere to pakker, hvor python3-pip vil blive trukket ind som en afhængighed.

sudo yum install python36 python3-virtualenv

Hvorfor man ikke bør bruge direkte opkald til modulet, som Langdon foreslår, og ikke installere pip3? I lyset af kommende automatiseringer er det kendt, at Ansible kræver, at pip er installeret, da pip-modulet ikke understøtter virtuelle miljøer (virtualenvs) med en tilpasset pip eksekverbar fil.

Med en fungerende python3-oversætter kan installationsprocessen af Django fortsætte, og du kan få et fungerende system sammen med vores andre komponenter. Der findes mange implementeringsmetoder på nettet. Her præsenteres én version, men brugere kan anvende deres egne processer.

Versionerne af PostgreSQL og Nginx, der er tilgængelige i RHEL 8 som standard, vil blive installeret med Yum.

sudo yum install nginx postgresql-server

For PostgreSQL vil psycopg2 være påkrævet, men det skal kun være tilgængeligt i virtualenv-miljøet, så det vil blive installeret via pip3 sammen med Django og Gunicorn. Men først skal vi konfigurere virtualenv.

Der er altid mange diskussioner om det rigtige valg af installationssted for Django-projekter, men når der er tvivl, kan man altid henvise til Linux Filesystem Hierarchy Standard. Især siger FHS, at /srv bruges til: "opbevaring af data, der er specifikke for en bestemt vært - data, som systemet leverer, såsom data og scripts fra webservere, data der opbevares på FTP-servere, samt repositories for versionskontrol (som tilkom i FHS-2.3 i 2004)."

Det er lige vores tilfælde, så vi lægger alt det nødvendige i /srv, hvis ejer er vores applikationsbruger (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

Opsætning af PostgreSQL og Django er ikke kompliceret: vi opretter en database, opretter en bruger, konfigurerer rettigheder. Der er et punkt, man bør huske ved den oprindelige installation af PostgreSQL – det er postgresql-setup-scriptet, som installeres sammen med postgresql-server-pakken. Dette script hjælper med at udføre grundlæggende opgaver relateret til administration af databasens klynge, såsom initialisering af klyngen eller opdateringsprocessen. For at konfigurere en ny PostgreSQL-instans på et RHEL-system, skal vi køre kommandoen:

sudo /usr/bin/postgresql-setup -initdb

Efter dette kan PostgreSQL startes med systemd, opret en database og konfigurer projektet i Django. Glem ikke at genstarte PostgreSQL, efter du har ændret konfigurationsfilen for klientautentifikation (normalt pg_hba.conf) for at indstille opbevaring af adgangskoden for applikationsbrugeren. Hvis du oplever andre problemer, skal du sikre dig, at IPv4- og IPv6-indstillingerne i pg_hba.conf-filen er ændret.

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

I filen /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

I filen /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 }}',
   }
}

Efter konfigurationen af settings.py-filen i projektet og opsætningen af databasekonfigurationen, kan udviklingsserveren startes for at sikre, at alt fungerer. Når udviklingsserveren er startet, kan det være en god idé at oprette en administratorkonto for at teste forbindelsen til databasen.

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

WSGI? Hvad?

Udviklingsserveren er nyttig til test, men for at køre applikationen skal den relevante server og proxy konfigureres til Web Server Gateway Interface (WSGI). Der er flere velkendte kombinationer, såsom Apache HTTPD med uWSGI eller Nginx med Gunicorn.

Web Server Gateway Interfaces opgave er at videresende anmodninger fra webserver til web-rammeværket Python. WSGI er en slags arv fra en forfærdelig fortid, hvor CGI-mekanismer var almindelige, og i dag er WSGI faktisk en standard, uanset hvilken webserver eller Python-ramme der anvendes. Men på trods af dets brede udbredelse er der stadig mange nuancer ved arbejdet med disse rammer og mange muligheder for valg. I dette tilfælde vil vi forsøge at etablere interaktionen mellem Gunicorn og Nginx via en socket.

Da begge disse komponenter er installeret på den samme server, vil vi forsøge at bruge en UNIX-socket i stedet for en netværkssocket. Da der uanset hvad er behov for en socket til kommunikation, vil vi forsøge at tage et skridt videre og konfigurere aktiveringen af socketen for Gunicorn gennem systemd.

Processen med at oprette socket-aktiverede tjenester (socket activated services) er ret enkel. Først oprettes en unit-fil, der indeholder direktivet ListenStream, som angiver punktet, hvor UNIX-socketen vil blive oprettet, derefter – en unit-fil for tjenesten, hvor direktivet Requires angiver unit-filen for socketen. Herefter er der bare at kalde Gunicorn fra det virtuelle miljø i unit-filen for tjenesten og oprette WSGI-bindingen til UNIX-socketen og Django-applikationen.

Her er et par eksempler på unit-filer, som du kan tage som udgangspunkt. Først konfigurerer vi socketen.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Nu er det nødvendigt at konfigurere Gunicorn-dæmonen.

[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

For Nginx er det tilstrækkeligt blot at oprette proxy-konfigurationsfiler og konfigurere mappen til opbevaring af statisk indhold, hvis du bruger det. På RHEL ligger Nginx-konfigurationsfilerne i /etc/nginx/conf.d. Du kan kopiere det følgende eksempel til filen /etc/nginx/conf.d/default.conf og starte tjenesten. Sørg for at angive server_name i overensstemmelse med navnet på dit vært.

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 Gunicorn og Nginx socketen med systemd, og du kan begynde at teste.

Fejl 502 Bad Gateway?

Hvis du indtaster adressen i browseren, vil du højst sandsynligt få fejl 502 Bad Gateway. Dette kan skyldes forkert konfigurerede tilladelser for UNIX-socket'en eller være relateret til mere komplekse problemer med adgangsstyring i SELinux.

I nginx-fejlloggen kan du finde en linje, der ligner denne:

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"

Hvis vi tester Gunicorn direkte, vil vi få et tomt svar.

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

Lad os finde ud af, hvorfor dette sker. Hvis vi åbner loggen, vil vi højst sandsynligt se, at problemet er relateret til SELinux. Da vi har en daemon kørende, som ikke har fået oprettet sin egen politik, bliver den markeret som init_t. Lad os teste denne teori i praksis.

sudo setenforce 0

Alt dette kan føre til kritik og blodige tårer, men det er kun prototypefejlretning. Lad os deaktivere kontrollen for at bekræfte, at problemet faktisk ligger her, hvorefter vi vender alt tilbage til sin oprindelige tilstand.

Efter at have opdateret siden i browseren eller genstartet vores curl-kommando, kan vi se test-siden for Django.

Så, når vi har bekræftet, at alt fungerer og ikke længere er nogen tilladelsesproblemer, genaktiverer vi SELinux.

sudo setenforce 1

Der vil ikke blive fortalt om audit2allow og oprettelse af politikker baseret på advarsler ved hjælp af sepolgen, da der på nuværende tidspunkt ikke er nogen faktisk Django-applikation, og derfor ikke er et fuldt kort over, hvad Gunicorn måtte ønske at få adgang til, og hvad denne adgang bør nægtes. Derfor er det nødvendigt at bevare SELinux's funktion for at beskytte systemet, mens man samtidig tillader applikationen at køre og generere logbeskeder, så der senere kan oprettes en reel politik baseret på dem.

Angivelse af tilladte domæner (permissive domains)

Ikke alle har hørt om tilladte domæner i SELinux, men der er ikke noget nyt ved dem. Mange har endda arbejdet med dem uden at vide det. Når en politik oprettes baseret på auditmeddelelser, repræsenterer den oprettede politik et tilladt domæne. Lad os prøve at oprette en simpel tilladende politik.

For at oprette et specifikt tilladt domæne for Gunicorn kræves der en politik, og de relevante filer skal også mærkes. Derudover er der behov for værktøjer til at samle nye politikker.

sudo yum install selinux-policy-devel

Mekanismen til tilladte domæner er et fremragende værktøj til at identificere problemer, især når det drejer sig om brugerdefinerede applikationer eller applikationer, der leveres uden allerede oprettede politikker. I dette tilfælde vil politikken for det tilladte domæne for Gunicorn være meget enkel – vi erklærer den primære type (gunicorn_t), erklærer den type, vi vil bruge til at mærke flere eksekverbare filer (gunicorn_exec_t), og derefter konfigurerer vi overgangen (transition) for systemet, så kørende processer bliver korrekt mærket. Den sidste linje indstiller politikken som tilladt som standard ved indlæsning.

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;

Denne politikfil kan kompilere og tilføje til systemet.

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

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

Lad os tjekke, om SELinux blokerer for noget andet end det, som vores ukendte dæmon henvender sig til.

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 tillader ikke Nginx at skrive data til UNIX-socketen, der bruges af Gunicorn. Normalt begynder man at ændre politikker i sådanne situationer, men der er også andre opgaver, der skal løses. Man kan også ændre domæneindstillingerne, så det går fra at være et begrænsningsdomæne til et tilladelsesdomæne. Vi skal nu overføre httpd_t til tilladelsesdomænet. Dette vil give Nginx den nødvendige adgang, så vi kan fortsætte med debugningen.

sudo semanage permissive -a httpd_t

Så når vi har formået at bevare beskyttelsen af SELinux (man bør faktisk ikke efterlade et projekt med SELinux i begrænsningsmode) og tilladelsesdomænerne indlæses, er det nødvendigt at finde ud af, hvad der præcist skal mærkes som gunicorn_exec_t, så alt fungerer som det skal igen. Lad os prøve at tilgå webstedet for at se nye adgangsbegrænsninger.

sudo ausearch -m AVC -c gunicorn

Man kan se mange meddelelser, der indeholder ‘comm="gunicorn"’, som udfører forskellige handlinger på filer i /srv/djangoapp, så det er tydeligt, at dette er en af de kommandoer, der bør mærkes.

Men derudover dukker der en meddelelse op af denne art:

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

Hvis man ser status for gunicorn-tjenesten eller udfører ps-kommandoen, vil ingen kørende processer blive vist. Det lader til, at gunicorn forsøger at tilgå Python-fortolker i vores virtualenv-miljø, muligvis for at starte worker-scripts. Derfor vil vi nu mærke disse to eksekverbare filer og se, om vi kan åbne vores test-Django-side.

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

Det er nødvendigt at genstarte gunicorn-tjenesten, så den nye etiket kan vælges. Den kan genstartes straks eller stoppes, så socketen kan starte den, når siden åbnes i browseren. Sørg for, at processerne har fået de nødvendige etiketter ved at bruge ps.

ps -efZ | grep gunicorn

Husk også at oprette en ordentlig SELinux-politik!

Hvis man ser AVC-meddelelserne nu, så indeholder den seneste meddelelse permissive=1 for alt, der relaterer til applikationen, og permissive=0 for hele resten af systemet. Hvis man forstår, hvilken adgang den rigtige applikation har brug for, kan man hurtigere finde en optimal løsning på sådanne problemer. Men indtil da er det bedre, at systemet er sikret, så man kan få en klar og anvendelig audit af Django-projektet.

sudo ausearch -m AVC

Det lykkedes!

Der er nu et fungerende Django-projekt med frontend på Nginx og Gunicorn WSGI. Vi har opsat Python 3 og PostgreSQL 10 fra RHEL 8 Beta-repositories. Nu kan vi gå videre og skabe (eller blot deployere) Django-applikationer eller undersøge andre tilgængelige værktøjer i RHEL 8 Beta til automatisering af opsætningsprocessen, forbedring af ydeevnen eller endda containerisering af denne konfiguration.

Kilde: habr.com

Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere 🔥 Køb pålidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster