Workshop RHEL 8 Beta: creazione di applicazioni Web funzionanti

RHEL 8 Beta offre agli sviluppatori molte nuove funzionalità, il cui elenco potrebbe richiedere pagine, tuttavia, imparare cose nuove è sempre meglio nella pratica, quindi di seguito offriamo un workshop sulla creazione effettiva di un'infrastruttura applicativa basata su Red Hat Enterprise Linux 8 Beta.

Workshop RHEL 8 Beta: creazione di applicazioni Web funzionanti

Prendiamo come base Python, un linguaggio di programmazione popolare tra gli sviluppatori, una combinazione di Django e PostgreSQL, una combinazione abbastanza comune per la creazione di applicazioni, e configuriamo RHEL 8 Beta per lavorare con loro. Quindi aggiungeremo un altro paio di ingredienti (non classificati).

L'ambiente di test cambierà, perché è interessante esplorare le possibilità dell'automazione, lavorando con container e provando ambienti con più server. Per iniziare con un nuovo progetto, puoi iniziare creando a mano un piccolo e semplice prototipo in modo da poter vedere esattamente cosa deve accadere e come interagisce, per poi passare all'automazione e alla creazione di configurazioni più complesse. Oggi parliamo della creazione di un simile prototipo.

Iniziamo distribuendo l'immagine della VM RHEL 8 Beta. Puoi installare una macchina virtuale da zero o utilizzare l'immagine guest KVM disponibile con il tuo abbonamento Beta. Quando si utilizza un'immagine guest, sarà necessario configurare un CD virtuale che conterrà metadati e dati utente per l'inizializzazione del cloud (cloud-init). Non è necessario fare nulla di speciale con la struttura del disco o con i pacchetti disponibili; va bene qualsiasi configurazione.

Diamo uno sguardo più da vicino all'intero processo.

Installazione di Django

Con la versione più recente di Django, avrai bisogno di un ambiente virtuale (virtualenv) con Python 3.5 o successivo. Nelle note della Beta puoi vedere che è disponibile Python 3.6, controlliamo se è effettivamente così:

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

Red Hat utilizza attivamente Python come toolkit di sistema in RHEL, quindi perché questo risultato?

Il fatto è che molti sviluppatori Python stanno ancora contemplando la transizione da Python 2 a Python 2, mentre lo stesso Python 3 è in fase di sviluppo attivo e appaiono costantemente sempre più nuove versioni. Pertanto, per soddisfare la necessità di strumenti di sistema stabili offrendo allo stesso tempo agli utenti l'accesso a varie nuove versioni di Python, il sistema Python è stato spostato in un nuovo pacchetto e ha fornito la possibilità di installare sia Python 2.7 che 3.6. Maggiori informazioni sulle modifiche e sul motivo per cui sono state apportate sono disponibili nella pubblicazione in Il blog di Langdon White (Langdon Bianco).

Quindi, per far funzionare Python, devi solo installare due pacchetti, con python3-pip incluso come dipendenza.

sudo yum install python36 python3-virtualenv

Perché non utilizzare le chiamate dirette ai moduli come suggerisce Langdon e installare pip3? Tenendo presente l'imminente automazione, è noto che Ansible richiederà l'installazione di pip per funzionare, poiché il modulo pip non supporta virtualenvs con un eseguibile pip personalizzato.

Con un interprete Python3 funzionante a tua disposizione, puoi continuare con il processo di installazione di Django e avere un sistema funzionante insieme agli altri nostri componenti. Ci sono molte opzioni di implementazione disponibili su Internet. Esiste una versione presentata qui, ma gli utenti possono utilizzare i propri processi.

Installeremo le versioni PostgreSQL e Nginx disponibili in RHEL 8 per impostazione predefinita utilizzando Yum.

sudo yum install nginx postgresql-server

PostgreSQL richiederà psycopg2, ma deve essere disponibile solo in un ambiente virtualenv, quindi lo installeremo utilizzando pip3 insieme a Django e Gunicorn. Ma prima dobbiamo configurare virtualenv.

Si discute sempre molto sulla scelta del posto giusto in cui installare i progetti Django, ma in caso di dubbi è sempre possibile rivolgersi al Linux Filesystem Hierarchy Standard. Nello specifico, l'FHS afferma che /srv viene utilizzato per: "memorizzare dati specifici dell'host: dati prodotti dal sistema, come dati e script del server Web, dati archiviati su server FTP e repository di sistema di controllo." -2.3 nel 2004)."

Questo è esattamente il nostro caso, quindi inseriamo tutto ciò di cui abbiamo bisogno in /srv, che è di proprietà dell'utente della nostra applicazione (utente cloud).

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

Configurare PostgreSQL e Django è semplice: crea un database, crea un utente, configura le autorizzazioni. Una cosa da tenere a mente quando si installa inizialmente PostgreSQL è lo script postgresql-setup installato con il pacchetto postgresql-server. Questo script consente di eseguire attività di base associate all'amministrazione del cluster di database, come l'inizializzazione del cluster o il processo di aggiornamento. Per configurare una nuova istanza PostgreSQL su un sistema RHEL, dobbiamo eseguire il comando:

sudo /usr/bin/postgresql-setup -initdb

Puoi quindi avviare PostgreSQL utilizzando systemd, creare un database e impostare un progetto in Django. Ricordarsi di riavviare PostgreSQL dopo aver apportato modifiche al file di configurazione dell'autenticazione del client (solitamente pg_hba.conf) per configurare l'archiviazione della password per l'utente dell'applicazione. Se incontri altre difficoltà, assicurati di modificare le impostazioni IPv4 e IPv6 nel file 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

Nel file /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

Nel file /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 }}',
   }
}

Dopo aver configurato il file settings.py nel progetto e aver impostato la configurazione del database, puoi avviare il server di sviluppo per assicurarti che tutto funzioni. Dopo aver avviato il server di sviluppo è opportuno creare un utente amministratore per testare la connessione al database.

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

WSGI? Vai?

Il server di sviluppo è utile per i test, ma per eseguire l'applicazione è necessario configurare il server e il proxy appropriati per WSGI (Web Server Gateway Interface). Esistono diverse combinazioni comuni, ad esempio Apache HTTPD con uWSGI o Nginx con Gunicorn.

Il compito dell'interfaccia Web Server Gateway è inoltrare le richieste dal server Web al framework Web Python. WSGI è una reliquia del terribile passato in cui esistevano i motori CGI, e oggi WSGI è lo standard de facto, indipendentemente dal server web o dal framework Python utilizzato. Ma nonostante il loro uso diffuso, quando si lavora con questi framework esistono ancora molte sfumature e molte scelte. In questo caso proveremo a stabilire l'interazione tra Gunicorn e Nginx tramite un socket.

Poiché entrambi questi componenti sono installati sullo stesso server, proviamo a utilizzare un socket UNIX invece di un socket di rete. Poiché la comunicazione richiede in ogni caso un socket, proviamo a fare un ulteriore passo e configurare l'attivazione del socket per Gunicorn tramite systemd.

Il processo di creazione di servizi attivati ​​tramite socket è abbastanza semplice. Innanzitutto, viene creato un file unit che contiene una direttiva ListenStream che punta al punto in cui verrà creato il socket UNIX, quindi un file unit per il servizio in cui la direttiva Requires punterà al file unit del socket. Quindi, nel file dell'unità di servizio, non resta che chiamare Gunicorn dall'ambiente virtuale e creare un collegamento WSGI per il socket UNIX e l'applicazione Django.

Ecco alcuni esempi di file unitari che puoi utilizzare come base. Per prima cosa impostiamo la presa.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Ora devi configurare il demone 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

Per Nginx, è semplice creare file di configurazione proxy e impostare una directory per archiviare il contenuto statico, se ne stai utilizzando uno. In RHEL, i file di configurazione di Nginx si trovano in /etc/nginx/conf.d. È possibile copiare il seguente esempio nel file /etc/nginx/conf.d/default.conf e avviare il servizio. Assicurati di impostare server_name in modo che corrisponda al tuo nome 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;
   }
}

Avvia il socket Gunicorn e Nginx utilizzando systemd e sei pronto per iniziare i test.

Errore gateway non valido?

Se inserisci l'indirizzo nel tuo browser, molto probabilmente riceverai un errore 502 Bad Gateway. Potrebbe essere causato da permessi socket UNIX configurati in modo errato o potrebbe essere dovuto a problemi più complessi relativi al controllo degli accessi in SELinux.

Nel registro degli errori di nginx puoi vedere una riga come questa:

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"

Se testiamo direttamente Gunicorn, otterremo una risposta vuota.

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

Scopriamo perché questo accade. Se apri il registro, molto probabilmente vedrai che il problema è legato a SELinux. Poiché stiamo eseguendo un demone per il quale non è stata creata alcuna policy, è contrassegnato come init_t. Proviamo questa teoria nella pratica.

sudo setenforce 0

Tutto ciò può causare critiche e lacrime di sangue, ma questo è solo il debug del prototipo. Disattiviamo il controllo solo per assicurarci che sia questo il problema, dopodiché riporteremo tutto al suo posto.

Aggiornando la pagina nel browser o eseguendo nuovamente il comando curl, puoi visualizzare la pagina di test di Django.

Quindi, dopo esserci assicurati che tutto funzioni e che non ci siano più problemi di permessi, abilitiamo nuovamente SELinux.

sudo setenforce 1

Non parlerò di audit2allow o della creazione di policy basate su avvisi con sepolgen qui, poiché al momento non esiste un'effettiva applicazione Django, quindi non esiste una mappa completa di ciò a cui Gunicorn potrebbe voler accedere e a cosa dovrebbe negare l'accesso. Pertanto, è necessario mantenere SELinux in esecuzione per proteggere il sistema, consentendo allo stesso tempo all'applicazione di essere eseguita e lasciare messaggi nel registro di controllo in modo che da essi possa poi essere creata la politica effettiva.

Specificazione di domini permissivi

Non tutti hanno sentito parlare di domini consentiti in SELinux, ma non sono una novità. Molti addirittura lavoravano con loro senza nemmeno rendersene conto. Quando viene creata una policy in base ai messaggi di controllo, la policy creata rappresenta il dominio risolto. Proviamo a creare una semplice politica di autorizzazione.

Per creare un dominio consentito specifico per Gunicorn, è necessaria una sorta di politica e inoltre è necessario contrassegnare i file appropriati. Inoltre, sono necessari strumenti per mettere a punto nuove politiche.

sudo yum install selinux-policy-devel

Il meccanismo dei domini consentiti è un ottimo strumento per identificare i problemi, soprattutto quando si tratta di un'applicazione personalizzata o di applicazioni fornite senza policy già create. In questo caso, la policy del dominio consentito per Gunicorn sarà la più semplice possibile: dichiarare un tipo principale (gunicorn_t), dichiarare un tipo che utilizzeremo per contrassegnare più eseguibili (gunicorn_exec_t), quindi impostare una transizione affinché il sistema contrassegni correttamente processi in esecuzione. L'ultima riga imposta la policy come abilitata per impostazione predefinita al momento del caricamento.

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;

Puoi compilare questo file di policy e aggiungerlo al tuo sistema.

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

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

Controlliamo per vedere se SELinux sta bloccando qualcos'altro oltre a ciò a cui accede il nostro demone sconosciuto.

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 impedisce a Nginx di scrivere dati sul socket UNIX utilizzato da Gunicorn. Tipicamente, in questi casi, le politiche cominciano a cambiare, ma ci sono altre sfide da affrontare. Puoi anche modificare le impostazioni del dominio da un dominio di restrizione a un dominio di autorizzazione. Ora spostiamo httpd_t nel dominio delle autorizzazioni. Ciò fornirà a Nginx l'accesso necessario e potremo continuare con ulteriore lavoro di debug.

sudo semanage permissive -a httpd_t

Quindi, una volta che sei riuscito a mantenere SELinux protetto (non dovresti davvero lasciare un progetto SELinux in modalità limitata) e i domini di autorizzazione sono stati caricati, devi capire cosa deve essere esattamente contrassegnato come gunicorn_exec_t per far funzionare tutto correttamente Ancora. Proviamo a visitare il sito Web per visualizzare nuovi messaggi sulle restrizioni di accesso.

sudo ausearch -m AVC -c gunicorn

Vedrai molti messaggi contenenti 'comm="gunicorn"' che eseguono varie operazioni sui file in /srv/djangoapp, quindi questo è ovviamente uno dei comandi che vale la pena segnalare.

Ma in più, appare un messaggio come questo:

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

Se guardi lo stato del servizio gunicorn o esegui il comando ps, non vedrai alcun processo in esecuzione. Sembra che gunicorn stia tentando di accedere all'interprete Python nel nostro ambiente virtualenv, possibilmente per eseguire script di lavoro. Quindi ora contrassegniamo questi due file eseguibili e controlliamo se possiamo aprire la nostra pagina di test di Django.

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

Il servizio gunicorn dovrà essere riavviato prima di poter selezionare il nuovo tag. Puoi riavviarlo immediatamente o interrompere il servizio e lasciare che sia il socket ad avviarlo quando apri il sito nel browser. Verificare che i processi abbiano ricevuto le etichette corrette utilizzando ps.

ps -efZ | grep gunicorn

Non dimenticare di creare una normale policy SELinux in seguito!

Se guardi ora i messaggi AVC, l'ultimo messaggio contiene permissive=1 per tutto ciò che riguarda l'applicazione e permissive=0 per il resto del sistema. Se capisci di che tipo di accesso ha bisogno un'applicazione reale, puoi trovare rapidamente il modo migliore per risolvere tali problemi. Ma fino ad allora, è meglio mantenere il sistema sicuro e ottenere un controllo chiaro e utilizzabile del progetto Django.

sudo ausearch -m AVC

Si è scoperto!

È apparso un progetto Django funzionante con un frontend basato su Nginx e Gunicorn WSGI. Abbiamo configurato Python 3 e PostgreSQL 10 dai repository RHEL 8 Beta. Ora puoi andare avanti e creare (o semplicemente distribuire) applicazioni Django o esplorare altri strumenti disponibili in RHEL 8 Beta per automatizzare il processo di configurazione, migliorare le prestazioni o persino containerizzare questa configurazione.

Fonte: habr.com

Aggiungi un commento