Práctica RHEL 8 Beta: Construyendo aplicaciones web funcionales

RHEL 8 Beta ofrece a los desarrolladores muchas nuevas oportunidades, cuyas enumeraciones podrían ocupar páginas enteras. Sin embargo, siempre es mejor aprender haciendo, así que a continuación proponemos un taller práctico sobre la creación de infraestructura de aplicaciones en Red Hat Enterprise Linux 8 Beta.

Práctica RHEL 8 Beta: Construyendo aplicaciones web funcionales

Tomaremos como base Python, un lenguaje de programación popular entre los desarrolladores, la combinación de Django y PostgreSQL, un conjunto bastante común para la creación de aplicaciones, y configuraremos RHEL 8 Beta para trabajar con ellos. Luego añadiremos un par de ingredientes más (no secretos).

El entorno de prueba cambiará, ya que es interesante explorar las posibilidades de automatización, trabajar con contenedores y probar entornos con múltiples servidores. Para comenzar con un nuevo proyecto, se puede empezar creando un pequeño prototipo simple de forma manual; así se puede ver lo que debería suceder y cómo se lleva a cabo la interacción, y luego pasar a la automatización y a la creación de configuraciones más complejas. Hoy hablaremos sobre la creación de dicho prototipo.

Comencemos desplegando la imagen de la máquina virtual RHEL 8 Beta VM. Se puede instalar la máquina virtual desde cero o utilizar la imagen invitada de KVM, disponible con la suscripción a Beta. Al utilizar la imagen invitada, se requerirá configurar un CD virtual que contenga los metadatos y los datos de usuario para la inicialización en la nube (cloud-init). No es necesario hacer nada especial con la estructura del disco o los paquetes disponibles; cualquier configuración será adecuada.

Veamos todo el proceso en más detalle.

Instalación de Django

Con la versión más reciente de Django, se necesita un entorno virtual (virtualenv) con Python 3.5 o una versión posterior. En las notas de la Beta se puede ver que Python 3.6 está disponible, así que verifiquemos si es cierto:

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

Red Hat utiliza activamente Python como herramienta del sistema en RHEL, así que ¿por qué obtenemos este resultado?

El hecho es que muchos desarrolladores que utilizan Python todavía están considerando la transición de Python 2 a Python 2, mientras que Python 3 está en desarrollo activo y constantemente se lanzan nuevas versiones. Por lo tanto, para satisfacer la necesidad de herramientas del sistema estables y al mismo tiempo ofrecer a los usuarios acceso a varias versiones nuevas de Python, Python del sistema se trasladó a un nuevo paquete y se facilitó la instalación tanto de Python 2.7 como de 3.6. Más información sobre los cambios y por qué se realizó esto se puede encontrar en la publicación en el blog de Langdon White (Langdon White).

Así que, para obtener Python en funcionamiento, solo es necesario instalar dos paquetes, mientras que python3-pip se instalará como una dependencia.

sudo yum install python36 python3-virtualenv

¿Por qué no deberías utilizar llamadas directas al módulo, como sugiere Langdon, y no instalar pip3? Teniendo en cuenta la automatización que se avecina, se sabe que para que Ansible funcione se necesita pip instalado, ya que el módulo pip no admite entornos virtuales (virtualenvs) con un archivo ejecutable personalizado pip.

Con un intérprete python3 funcionando, puedes continuar el proceso de instalación de Django y obtener un sistema en funcionamiento junto con otros de nuestros componentes. Hay muchas implementaciones disponibles en la red. Aquí se presenta una versión, pero los usuarios pueden utilizar sus propios procesos.

Las versiones de PostgreSQL y Nginx disponibles por defecto en RHEL 8 se instalarán usando Yum.

sudo yum install nginx postgresql-server

Para PostgreSQL se necesitará psycopg2, pero debe estar disponible solo en el entorno virtualenv, por lo que lo instalaremos usando pip3 junto con Django y Gunicorn. Pero primero necesitamos configurar virtualenv.

Hay mucha discusión sobre la elección correcta del lugar para instalar proyectos Django, pero cuando surgen dudas, siempre se puede recurrir al estándar Linux Filesystem Hierarchy Standard. En particular, el FHS establece que /srv se utiliza para: ‘almacenar datos específicos de un nodo, es decir, datos que proporciona el sistema, como los datos y scripts de los servidores web, datos almacenados en servidores FTP, así como repositorios de sistemas de control de versiones (introducidos en FHS-2.3 en 2004)’.

Este es nuestro caso, así que recopilamos todo lo necesario en /srv, cuyo propietario es nuestro usuario de aplicación (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

Configurar PostgreSQL y Django es sencillo: creamos una base de datos, creamos un usuario y configuramos los permisos. Hay un punto que se debe recordar al instalar PostgreSQL: el script postgresql-setup, que se instala con el paquete postgresql-server. Este script ayuda a realizar tareas básicas relacionadas con la administración del clúster de bases de datos, como la inicialización del clúster o el proceso de actualización. Para configurar una nueva instancia de PostgreSQL en un sistema RHEL, debemos ejecutar el siguiente comando:

sudo /usr/bin/postgresql-setup -initdb

Después de esto, podemos iniciar PostgreSQL usando systemd, crear una base de datos y configurar el proyecto en Django. No olvides reiniciar PostgreSQL después de realizar cambios en el archivo de configuración de autenticación del cliente (normalmente pg_hba.conf) para ajustar el almacenamiento de la contraseña para el usuario de la aplicación. Si encuentras otras dificultades, asegúrate de que se han modificado las configuraciones de IPv4 e IPv6 en el archivo 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

En el archivo /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

En el archivo /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 }}',
   }
}

Después de configurar el archivo settings.py en el proyecto y ajustar la configuración de la base de datos, puedes iniciar el servidor de desarrollo para asegurarte de que todo funciona. Tras iniciar el servidor de desarrollo, sería bueno crear un usuario admin para probar la conexión a la base de datos.

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

¿WSGI? ¿Qué es eso?

El servidor de desarrollo es útil para pruebas, pero para lanzar la aplicación es necesario configurar el servidor y el proxy adecuados para la Interfaz de Puerta de Servidor Web (WSGI). Hay varias combinaciones comunes, como Apache HTTPD con uWSGI o Nginx con Gunicorn.

La tarea de la Interfaz de Puerta de Servidor Web es redirigir las solicitudes desde de un servidor web al marco web de Python. WSGI es un vestigio de un pasado problemático, cuando los mecanismos CGI eran comunes, y hoy WSGI es efectivamente un estándar, independientemente del servidor web o del marco de Python utilizados. Sin embargo, a pesar de su amplia difusión, todavía existen muchos matices al trabajar con estos marcos y numerosas opciones de elección. En este caso, intentaremos establecer la interacción entre Gunicorn y Nginx a través de un socket.

Dado que ambos componentes están instalados en el mismo servidor, intentaremos usar un socket UNIX en lugar de un socket de red. Dado que en cualquier caso se necesita un socket para la comunicación, haremos un paso más y configuraremos la activación del socket para Gunicorn a través de systemd.

El proceso de creación de servicios activados por sockets es bastante sencillo. Primero, se crea un archivo unit que contiene la directiva ListenStream, que indica el punto en el que se creará el socket UNIX, luego un archivo unit para el servicio, en el que la directiva Requires apuntará al archivo unit del socket. Luego, en el archivo unit del servicio, solo quedará llamar a Gunicorn desde el entorno virtual y crear un enlace WSGI para el socket UNIX y la aplicación Django.

Aquí hay algunos ejemplos de archivos unit que se pueden usar como base. Primero configuramos el socket.

[Unit]
Description=Socket WSGI de Gunicorn

[Socket]
ListenStream=\/run\/gunicorn.sock

[Install]
WantedBy=sockets.target

Ahora es necesario configurar el demonio Gunicorn.

[Unit]
Description=Demonio de 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

Para Nginx, basta con crear los archivos de configuración del proxy y configurar el directorio para almacenar el contenido estático, si lo utiliza. En RHEL, los archivos de configuración de Nginx se encuentran en \/etc\/nginx\/conf.d. Puede copiar el siguiente ejemplo en el archivo \/etc\/nginx\/conf.d\/default.conf y activar el servicio. Asegúrese de haber especificado server_name de acuerdo con el nombre de su 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;
   }
}

Inicie el socket de Gunicorn y Nginx utilizando systemd, y podrá comenzar a probar.

¿Error Bad Gateway?

Si introduces la dirección en el navegador, es probable que obtengas un error 502 Bad Gateway. Esto puede ser causado por permisos incorrectamente configurados para el socket UNIX o por problemas más complejos relacionados con la gestión de acceso en SELinux.

En el registro de errores de nginx, puedes encontrar una línea similar a esta:

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"

Si pruebas Gunicorn directamente, obtendrás una respuesta vacía.

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

Vamos a investigar por qué ocurre esto. Si abrimos el registro, es probable que veamos que el problema está relacionado con SELinux. Dado que tenemos un demonio en ejecución para el cual no se ha creado una política, se marca como init_t. Verifiquemos esta teoría en la práctica.

sudo setenforce 0

Todo esto puede causar críticas y lágrimas sanguinolentas, pero es solo una depuración del prototipo. Desactivamos la verificación solo para asegurarnos de que el problema está en esto, después de lo cual volveremos todo a su lugar.

Al actualizar la página en el navegador o reiniciar nuestro comando curl, podemos ver la página de prueba de Django.

Así que, asegurándonos de que todo funciona y que no hay más problemas de permisos, volvemos a activar SELinux.

sudo setenforce 1

Aquí no se discutirá sobre audit2allow y la creación de políticas basadas en las alertas con sepolgen, porque en este momento no hay una aplicación Django real, por lo que no hay un mapa completo de a qué puede querer acceder Gunicorn y a qué se debe prohibir dicho acceso. Por lo tanto, es necesario mantener el funcionamiento de SELinux para proteger el sistema, y al mismo tiempo permitir que la aplicación se ejecute y registre mensajes en el registro de auditoría para que posteriormente se pueda crear una política real en base a ellos.

Especificación de dominios permitidos (permissive domains)

No todos han oído hablar de los dominios permitidos en SELinux, pero no hay nada nuevo en ellos. Muchos incluso han trabajado con ellos sin darse cuenta. Cuando se crea una política basada en mensajes de auditoría, la política creada representa un dominio permitido. Intentemos crear una política permisiva simple.

Para crear un dominio permitido específico para Gunicorn, se necesita una política y también se deben etiquetar los archivos correspondientes. Además, son necesarias herramientas para compilar nuevas políticas.

sudo yum install selinux-policy-devel

El mecanismo de dominios permitidos es una excelente herramienta para identificar problemas, especialmente cuando se trata de una aplicación personalizada o de aplicaciones que se entregan sin políticas ya creadas. En este caso, la política de dominio permitido para Gunicorn será lo más simple posible: declararemos el tipo principal (gunicorn_t), declararemos el tipo que usaremos para etiquetar varios archivos ejecutables (gunicorn_exec_t) y luego configuraremos la transición para system, para etiquetar correctamente los procesos en ejecución. La última línea establece la política como permitida por defecto en el momento de su carga.

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;

Se puede compilar este archivo de políticas y añadirlo 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

Verifiquemos si SELinux está bloqueando algo más, además de lo que accede nuestro demonio desconocido.

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 no permite que Nginx escriba datos en el socket UNIX utilizado por Gunicorn. Normalmente, en estos casos se comienzan a cambiar las políticas, pero hay otras tareas por resolver. También se pueden modificar las configuraciones del dominio, transformándolo de un dominio de restricciones a un dominio de permisos. Ahora trasladaremos httpd_t al dominio de permisos. Esto proporcionará a Nginx el acceso necesario y podremos continuar con el trabajo de depuración.

sudo semanage permissive -a httpd_t

Por lo tanto, una vez que hemos conseguido mantener la protección de SELinux (de hecho, no deberíamos dejar un proyecto con SELinux en modo restrictivo) y los dominios de permisos se cargan, es necesario averiguar qué debe ser etiquetado como gunicorn_exec_t para que todo vuelva a funcionar correctamente. Intentemos acceder al sitio web para ver nuevos mensajes sobre restricciones en el acceso.

sudo ausearch -m AVC -c gunicorn

Se pueden ver muchos mensajes que contienen ‘comm=«gunicorn»’, que realizan varias acciones sobre los archivos en /srv/djangoapp, por lo que, evidentemente, esta es una de las órdenes que vale la pena marcar.

Pero además, aparece un mensaje de este tipo:

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

Si se comprueba el estado del servicio gunicorn o se ejecuta el comando ps, no aparecerán procesos en ejecución. Parece que gunicorn está intentando acceder al intérprete de Python en nuestro entorno virtual, posiblemente para ejecutar scripts de trabajo (workers). Por lo tanto, ahora marcaremos estos dos archivos ejecutables y verificaremos si podemos abrir nuestra página de prueba de Django.

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

Será necesario reiniciar el servicio gunicorn para que pueda seleccionar la nueva etiqueta. Se puede reiniciar de inmediato o detener el servicio y permitir que el socket lo inicie al abrir el sitio en el navegador. Asegúrese de que los procesos hayan recibido las etiquetas necesarias utilizando ps.

ps -efZ | grep gunicorn

¡No olvide crear una política SELinux adecuada después!

Si se miran los mensajes AVC ahora, el último mensaje contiene permissive=1 para todo lo relacionado con la aplicación y permissive=0 para el resto del sistema. Si se comprende qué acceso necesita realmente la aplicación, se puede encontrar más rápido una solución óptima para problemas similares. Pero hasta entonces, es mejor que el sistema esté protegido y que se obtenga una auditoría clara y utilizable del proyecto Django.

sudo ausearch -m AVC

¡Lo logramos!

Se ha implementado un proyecto Django funcional con frontend en Nginx y Gunicorn WSGI. Hemos configurado Python 3 y PostgreSQL 10 desde los repositorios de RHEL 8 Beta. Ahora podemos avanzar y crear (o simplemente desplegar) aplicaciones Django o explorar otras herramientas disponibles en RHEL 8 Beta para automatizar el proceso de configuración, mejorar el rendimiento o incluso contenerizar esta configuración.

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster