RHEL 8 Beta offre agli sviluppatori molte nuove possibilità, la cui elencazione potrebbe richiedere pagine, tuttavia, è sempre meglio studiare il nuovo nella pratica, quindi di seguito proponiamo di seguire un pratico laboratorio sulla creazione reale di un'infrastruttura di applicazioni basata su Red Hat Enterprise Linux 8 Beta.

Partiremo da Python, un linguaggio di programmazione popolare tra gli sviluppatori, una combinazione di Django e PostgreSQL, un accoppiamento abbastanza comune per la creazione di applicazioni, e configureremo RHEL 8 Beta per lavorare con loro. Poi aggiungeremo un paio di ingredienti (non segreti).
L'ambiente di test cambierà, poiché è interessante esplorare le possibilità di automazione, lavorare con i contenitori e provare ambienti con più server. Per iniziare un nuovo progetto, si può iniziare creando un piccolo prototipo semplice manualmente: in questo modo si può vedere cosa deve realmente accadere e come avviene l'interazione, e poi passare all'automazione e alla creazione di configurazioni più complesse. Oggi parleremo della creazione di tale prototipo.
Iniziamo con il dispiegamento dell'immagine della macchina virtuale RHEL 8 Beta VM. È possibile installare la macchina virtuale da zero o utilizzare l'immagine guest KVM, disponibile con l'iscrizione alla Beta. Utilizzando l'immagine guest, è necessario configurare un CD virtuale che conterrà i metadati e i dati utente per l'inizializzazione cloud (cloud-init). Non è necessario fare nulla di particolare con la struttura del disco o con i pacchetti disponibili, qualsiasi configurazione andrà bene.
Esaminiamo l'intero processo più in dettaglio.
Installazione di Django
Con la versione più recente di Django avremo bisogno di un ambiente virtuale (virtualenv) con Python 3.5 o versioni successive. Nelle note della Beta si può vedere che è disponibile Python 3.6, controlliamo se è davvero 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 strumento di sistema in RHEL, quindi perché si ottiene questo risultato?
Il fatto è che molti sviluppatori che utilizzano Python stanno ancora considerando di passare da Python 2 a Python 2, mentre Python 3 è attualmente in fase di sviluppo attivo, con nuove versioni che vengono rilasciate continuamente. Pertanto, per soddisfare la necessità di strumenti di sistema stabili e nel contempo offrire agli utenti l'accesso a varie nuove versioni di Python, il Python di sistema è stato trasferito in un nuovo pacchetto, garantendo la possibilità di installare sia Python 2.7 che 3.6. Maggiori informazioni sulle modifiche e sul perché siano state effettuate possono essere reperite nella pubblicazione nel (Langdon White).
Quindi, per ottenere un Python funzionante, è necessario installare solo due pacchetti, con python3-pip che verrà tirato in quanto dipendenza.
sudo yum install python36 python3-virtualenv
Perché non dovresti utilizzare accessi diretti al modulo, come suggerisce Langdon, e non installare pip3? Ricordando l'automazione imminente, è noto che per far funzionare Ansible è necessario avere pip installato, poiché il modulo pip non supporta ambienti virtuali (virtualenvs) con un eseguibile pip personalizzato.
Avendo a disposizione un interprete python3 funzionante, puoi continuare il processo di installazione di Django e ottenere un sistema funzionante insieme agli altri nostri componenti. Sono disponibili molte implementazioni in rete. Qui è presentata una versione, ma gli utenti possono utilizzare i propri processi.
Installeremo le versioni di PostgreSQL e Nginx disponibili in RHEL 8 di default utilizzando Yum.
sudo yum install nginx postgresql-server
Per PostgreSQL sarà necessario psycopg2, ma deve essere disponibile solo nell'ambiente virtualenv, quindi lo installeremo utilizzando pip3 insieme a Django e Gunicorn. Ma prima dobbiamo configurare virtualenv.
Si discute molto sull'opportuna scelta del luogo di installazione dei progetti Django, ma quando sorgono dubbi, è sempre possibile fare riferimento allo standard Linux Filesystem Hierarchy Standard. In particolare, il FHS afferma che /srv è utilizzato per: «memorizzare i dati specifici di un nodo, ovvero dati forniti dal sistema, come dati e script dei server web, dati memorizzati sui server FTP, e repository dei sistemi di controllo versione (introdotti nel FHS-2.3 nel 2004)».
Questo è proprio il nostro caso, quindi mettiamo tutto il necessario in /srv, di cui il proprietario è il nostro utente applicativo (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
La configurazione di PostgreSQL e Django non presenta difficoltà: creiamo un database, creiamo un utente, impostiamo i permessi. C'è un aspetto da tenere presente durante l'installazione iniziale di PostgreSQL: è lo script postgresql-setup, che viene installato con il pacchetto postgresql-server. Questo script aiuta a eseguire le attività di base relative all'amministrazione del cluster di database, come l'inizializzazione del cluster o il processo di aggiornamento. Per configurare una nuova istanza di PostgreSQL su un sistema RHEL, dobbiamo eseguire il comando:
sudo /usr/bin/postgresql-setup -initdb
Dopo di che, è possibile avviare PostgreSQL utilizzando systemd, creare il database e configurare il progetto in Django. Non dimenticate di riavviare PostgreSQL dopo aver apportato modifiche al file di configurazione dell'autenticazione del client (di solito pg_hba.conf) per configurare la memorizzazione della password per l'utente applicativo. Se riscontrate altre difficoltà, assicuratevi che le impostazioni di IPv4 e IPv6 siano state modificate 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 impostato la configurazione del database, è possibile avviare il server di sviluppo per assicurarsi che tutto funzioni. Dopo aver avviato il server di sviluppo, è utile creare un utente admin per testare la connessione al database.
./manage.py runserver 0.0.0.0:8000
./manage.py createsuperuser
WSGI? Che cos'è?
Il server di sviluppo è utile per il testing, ma per eseguire l'applicazione è necessario configurare il server e il proxy appropriati per il Web Server Gateway Interface (WSGI). Ci sono diverse combinazioni comuni, come Apache HTTPD con uWSGI o Nginx con Gunicorn.
Il compito del Web Server Gateway Interface è reindirizzare le richieste da web al framework web Python. WSGI è un'eredità del terribile passato, quando si utilizzavano i meccanismi CGI, e oggi WSGI è praticamente uno standard, indipendentemente dal server web o dal framework Python utilizzato. Nonostante la sua ampia diffusione, ci sono ancora molte sfumature nel lavorare con questi framework e molte opportunità di scelta. In questo caso, cercheremo di stabilire un'interazione tra Gunicorn e Nginx tramite socket.
Poiché entrambi questi componenti sono installati sullo stesso server, cercheremo di utilizzare un socket UNIX al posto di un socket di rete. Poiché in ogni caso è necessario un socket per la comunicazione, cercheremo di fare un ulteriore passo e configurare l'attivazione del socket per Gunicorn tramite systemd.
Il processo di creazione di servizi attivati da socket è abbastanza semplice. Prima si crea un file unit che contiene la direttiva ListenStream, che indica il punto in cui sarà creato il socket UNIX, poi si crea un file unit per il servizio, in cui la direttiva Requires farà riferimento al file unit del socket. Infine, nel file unit del servizio rimarrà solo da chiamare Gunicorn dall'ambiente virtuale e creare un binding WSGI per il socket UNIX e l'applicazione Django.
Ecco alcuni esempi di file unit che possono essere utilizzati come base. Prima configuriamo il socket.
[Unit]
Description=Socket WSGI di Gunicorn
[Socket]
ListenStream=\/run\/gunicorn.sock
[Install]
WantedBy=sockets.target
Ora è necessario configurare il demone Gunicorn.
[Unit]
Description=Demone di Gunicorn
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, è sufficiente creare i file di configurazione del proxy e configurare la directory per la memorizzazione dei contenuti statici, se la si utilizza. In RHEL, i file di configurazione di Nginx si trovano in \/etc\/nginx\/conf.d. Puoi copiare il seguente esempio nel file \/etc\/nginx\/conf.d\/default.conf e avviare il servizio. Assicurati di aver specificato il server_name in base al nome del tuo 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 tramite systemd, e puoi iniziare a testare.
Errore Bad Gateway?
Se inserisci l'indirizzo nel browser, è probabile che tu riceva un errore 502 Bad Gateway. Questo può essere causato da autorizzazioni configurate in modo errato per il socket UNIX, o da problemi più complessi legati alla gestione degli accessi in SELinux.
Nel registro degli errori di nginx puoi trovare una riga simile a 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 Gunicorn direttamente, otteniamo una risposta vuota.
curl —unix-socket /run/gunicorn.sock 8beta1.example.com
Cerchiamo di capire perché questo accade. Se apriamo il registro, probabilmente vedremo che il problema è correlato a SELinux. Poiché abbiamo un demone in esecuzione per il quale non è stata creata una propria policy, viene contrassegnato come init_t. Verifichiamo questa teoria in pratica.
sudo setenforce 0
Tutto questo può suscitare critiche e lacrime amare, ma è solo il debugging di un prototipo. Disabiliteremo il controllo solo per assicurarci che il problema sia proprio questo, dopodiché riporteremo tutto al suo posto.
Aggiornando la pagina nel browser o riavviando il nostro comando curl, possiamo vedere la pagina di test di Django.
Quindi, una volta verificato che tutto funziona e che non ci sono ulteriori problemi con le autorizzazioni, riattiviamo SELinux.
sudo setenforce 1
Qui non parleremo di audit2allow e della creazione di policy basate su avvisi attraverso sepolgen, poiché al momento non abbiamo un'applicazione Django reale, quindi non abbiamo una mappa completa di ciò a cui Gunicorn potrebbe voler accedere e a cosa è necessario negare l'accesso. Pertanto, è necessario mantenere attiva la protezione di SELinux per la sicurezza del sistema, e allo stesso tempo permettere all'applicazione di avviarsi e registrare messaggi nel registro audit, in modo da poter successivamente creare una policy reale sulla base di essi.
Specificazione dei domini permissivi
Non tutti hanno sentito parlare dei domini permissivi in SELinux, ma non c'è nulla di nuovo in essi. Molti hanno persino lavorato con essi senza rendersi conto. Quando viene creata una policy basata su messaggi di audit, la policy creata rappresenta un dominio permissivo. Proviamo a creare una semplice policy permissiva.
Per creare un dominio specifico autorizzato per Gunicorn, è necessaria una politica e dovranno essere contrassegnati i file appropriati. Inoltre, sono necessari strumenti per raccogliere nuove politiche.
sudo yum install selinux-policy-devel
Il meccanismo dei domini autorizzati è uno strumento eccellente per identificare problemi, specialmente quando si tratta di un'applicazione personalizzata o di applicazioni fornite senza politiche già create. In questo caso, la politica del dominio autorizzato per Gunicorn sarà molto semplice: dichiareremo il tipo principale (gunicorn_t), dichiareremo il tipo che utilizzeremo per contrassegnare diversi file eseguibili (gunicorn_exec_t) e poi imposteremo la transizione (transition) per il sistema, per contrassegnare correttamente i processi in esecuzione. L'ultima riga imposta la politica come autorizzata 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;
È possibile compilare questo file di politiche e aggiungerlo al sistema.
make -f /usr/share/selinux/devel/Makefile
sudo semodule -i gunicorn.pp
sudo semanage permissive -a gunicorn_t
sudo semodule -l | grep permissive
Verifichiamo se SELinux sta bloccando qualcos'altro oltre a ciò a cui il nostro daemon sconosciuto sta accedendo.
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 non consente a Nginx di scrivere dati nel socket UNIX utilizzato da Gunicorn. Di solito in questi casi si iniziano a modificare le politiche, ma ci sono altre questioni da risolvere. È possibile anche modificare le impostazioni del dominio, trasformandolo da dominio di restrizione a dominio di autorizzazione. Ora trasferiremo httpd_t nel dominio di autorizzazione. Questo garantirà a Nginx l'accesso necessario e potremo continuare il lavoro di debug.
sudo semanage permissive -a httpd_t
Quindi, quando si è riusciti a mantenere la protezione di SELinux (in realtà non si dovrebbe lasciare un progetto con SELinux in modalità restrittiva) e i domini di autorizzazione vengono caricati, è necessario scoprire cosa è necessario contrassegnare come gunicorn_exec_t affinché tutto torni a funzionare correttamente. Proviamo ad accedere al sito web per vedere i nuovi messaggi di restrizione degli accessi.
sudo ausearch -m AVC -c gunicorn
È possibile vedere numerosi messaggi contenenti ‘comm="gunicorn"’, che eseguono varie azioni sui file in /srv/djangoapp, quindi, ovviamente, questa è una delle comandi da contrassegnare.
Ma, oltre a ciò, appare un messaggio di questo tipo:
type=AVC msg=audit(1545320700.070:1542): avc: denied { execute } per 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 si controlla lo stato del servizio gunicorn o si esegue il comando ps, non apparirà alcun processo in esecuzione. Sembra che gunicorn stia cercando di accedere all'interprete Python nel nostro ambiente virtualenv, probabilmente per avviare gli script dei lavoratori (workers). Quindi, attualmente contrassegneremo questi due file eseguibili per verificare se riesci ad aprire la nostra pagina di test Django.
chcon -t gunicorn_exec_t /srv/djangoapp/django/bin/gunicorn /srv/djangoapp/django/bin/python3.6
Sarà necessario riavviare il servizio gunicorn per poter utilizzare la nuova etichetta. Puoi riavviarlo subito oppure fermare il servizio e lasciare che il socket lo avvii quando apri il sito nel browser. Assicurati che i processi abbiano ricevuto le etichette corrette utilizzando ps.
ps -efZ | grep gunicorn
Non dimenticare di creare poi una politica SELinux appropriata!
Se ora controlli i messaggi AVC, l'ultimo messaggio contiene permissive=1 per tutto ciò che riguarda l'applicazione e permissive=0 per il resto del sistema. Comprendendo quale accesso è necessario per l'applicazione reale, puoi trovare più rapidamente la soluzione ottimale a problemi simili. Ma fino ad allora, è meglio che il sistema sia protetto e che tu ottenga un audit comprensibile e utilizzabile sul progetto Django.
sudo ausearch -m AVC
Fatto!
È stato creato un progetto Django funzionante con frontend su Nginx e Gunicorn WSGI. Abbiamo configurato Python 3 e PostgreSQL 10 dai repository RHEL 8 Beta. Ora si può 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 addirittura containerizzare questa configurazione.
Fonte: habr.com
