Taller de RHEL 8 Beta: creación de aplicaciones web funcionales

RHEL 8 Beta ofrece a los desarrolladores muchas características nuevas, cuya lista podría ocupar páginas; sin embargo, aprender cosas nuevas siempre es mejor en la práctica, por lo que a continuación ofrecemos un taller sobre cómo crear una infraestructura de aplicaciones basada en Red Hat Enterprise Linux 8 Beta.

Taller de RHEL 8 Beta: creación de aplicaciones web funcionales

Tomemos como base Python, un lenguaje de programación popular entre los desarrolladores, una combinación de Django y PostgreSQL, una combinación bastante común para crear aplicaciones, y configuremos RHEL 8 Beta para trabajar con ellos. Luego agregaremos un par de ingredientes más (sin clasificar).

El entorno de prueba cambiará, porque es interesante explorar las posibilidades de la automatización, trabajar con contenedores y probar entornos con múltiples servidores. Para comenzar con un nuevo proyecto, puede comenzar creando un prototipo pequeño y simple a mano para que pueda ver exactamente qué debe suceder y cómo interactúa, y luego continuar para automatizar y crear configuraciones más complejas. Hoy estamos hablando de la creación de tal prototipo.

Comencemos implementando la imagen de VM Beta de RHEL 8. Puede instalar una máquina virtual desde cero o utilizar la imagen de invitado KVM disponible con su suscripción Beta. Cuando utilice una imagen invitada, deberá configurar un CD virtual que contendrá metadatos y datos de usuario para la inicialización en la nube (cloud-init). No necesita hacer nada especial con la estructura del disco o los paquetes disponibles; cualquier configuración servirá.

Echemos un vistazo más de cerca a todo el proceso.

Instalando Django

Con la versión más reciente de Django, necesitarás un entorno virtual (virtualenv) con Python 3.5 o posterior. En las notas Beta puedes ver que Python 3.6 está disponible, comprobemos si efectivamente es así:

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

Red Hat utiliza activamente Python como conjunto de herramientas del sistema en RHEL, entonces, ¿por qué ocurre esto?

El hecho es que muchos desarrolladores de Python todavía están contemplando la transición de Python 2 a Python 2, mientras que Python 3 está en desarrollo activo y constantemente aparecen más y más versiones nuevas. Por lo tanto, para satisfacer la necesidad de herramientas de sistema estables y al mismo tiempo ofrecer a los usuarios acceso a varias versiones nuevas de Python, el sistema Python se trasladó a un nuevo paquete y proporcionó la capacidad de instalar Python 2.7 y 3.6. Puede encontrar más información sobre los cambios y por qué se realizaron en la publicación en El blog de Langdon White. (Langdon Blanco).

Entonces, para que Python funcione, solo necesita instalar dos paquetes, con python3-pip incluido como dependencia.

sudo yum install python36 python3-virtualenv

¿Por qué no utilizar llamadas directas al módulo como sugiere Langdon e instalar pip3? Teniendo en cuenta la próxima automatización, se sabe que Ansible requerirá pip instalado para ejecutarse, ya que el módulo pip no admite virtualenvs con un ejecutable de pip personalizado.

Con un intérprete de Python3 funcional a su disposición, puede continuar con el proceso de instalación de Django y tener un sistema que funcione junto con nuestros otros componentes. Hay muchas opciones de implementación disponibles en Internet. Aquí se presenta una versión, pero los usuarios pueden usar sus propios procesos.

Instalaremos las versiones de PostgreSQL y Nginx disponibles en RHEL 8 de forma predeterminada usando Yum.

sudo yum install nginx postgresql-server

PostgreSQL requerirá psycopg2, pero debe estar disponible sólo en un entorno virtualenv, por lo que lo instalaremos usando pip3 junto con Django y Gunicorn. Pero primero necesitamos configurar virtualenv.

Siempre hay mucho debate sobre el tema de elegir el lugar correcto para instalar proyectos de Django, pero en caso de duda, siempre puedes recurrir al Estándar de jerarquía del sistema de archivos de Linux. Específicamente, la FHS dice que /srv se utiliza para: "almacenar datos específicos del host: datos que produce el sistema, como datos y scripts del servidor web, datos almacenados en servidores FTP y repositorios del sistema de control". -2.3 en 2004)."

Este es exactamente nuestro caso, por lo que ponemos todo lo que necesitamos en /srv, que es propiedad de nuestro usuario de la aplicación (usuario de la nube).

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 fácil: cree una base de datos, cree un usuario, configure permisos. Una cosa a tener en cuenta al instalar PostgreSQL inicialmente es el script de configuración de postgresql que se instala con el paquete postgresql-server. Este script le ayuda a realizar tareas básicas asociadas 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, necesitamos ejecutar el comando:

sudo /usr/bin/postgresql-setup -initdb

Luego puede iniciar PostgreSQL usando systemd, crear una base de datos y configurar un proyecto en Django. Recuerde reiniciar PostgreSQL después de realizar cambios en el archivo de configuración de autenticación del cliente (generalmente pg_hba.conf) para configurar el almacenamiento de contraseñas para el usuario de la aplicación. Si encuentra otras dificultades, asegúrese de cambiar la configuración 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 configurar la base de datos, puede iniciar el servidor de desarrollo para asegurarse de que todo funcione. Después de iniciar el servidor de desarrollo, es una buena idea crear un usuario administrador para probar la conexión a la base de datos.

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

¿WSGI? ¿Espere?

El servidor de desarrollo es útil para realizar pruebas, pero para ejecutar la aplicación debe configurar el servidor y el proxy adecuados para la interfaz de puerta de enlace del servidor web (WSGI). Existen varias combinaciones comunes, por ejemplo, Apache HTTPD con uWSGI o Nginx con Gunicorn.

El trabajo de la interfaz de puerta de enlace del servidor web es reenviar solicitudes desde el servidor web al marco web de Python. WSGI es una reliquia del terrible pasado cuando existían los motores CGI, y hoy en día WSGI es el estándar de facto, independientemente del servidor web o el marco Python utilizado. Pero a pesar de su uso generalizado, todavía existen muchos matices al trabajar con estos marcos y muchas opciones. En este caso, intentaremos establecer una interacción entre Gunicorn y Nginx a través de un socket.

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

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

A continuación se muestran algunos ejemplos de archivos unitarios que puede utilizar como base. Primero configuramos el enchufe.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Ahora necesitas configurar el demonio 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

Para Nginx, es una simple cuestión de crear archivos de configuración de proxy y configurar un directorio para almacenar contenido estático si está usando uno. 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 e iniciar el servicio. Asegúrese de configurar server_name para que coincida con su nombre de 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 Gunicorn y Nginx usando systemd y estará listo para comenzar a realizar pruebas.

¿Error de puerta de enlace incorrecta?

Si ingresa la dirección en su navegador, lo más probable es que reciba un error 502 Bad Gateway. Puede deberse a permisos de socket UNIX configurados incorrectamente o puede deberse a problemas más complejos relacionados con el control de acceso en SELinux.

En el registro de errores de nginx puedes ver una línea como 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 probamos Gunicorn directamente, obtendremos una respuesta vacía.

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

Averigüemos por qué sucede esto. Si abre el registro, lo más probable es que vea que el problema está relacionado con SELinux. Dado que estamos ejecutando un demonio para el que no se ha creado ninguna política, está marcado como init_t. Probemos esta teoría en la práctica.

sudo setenforce 0

Todo esto puede provocar críticas y lágrimas de sangre, pero esto es sólo una depuración del prototipo. Desactivemos la verificación solo para asegurarnos de que este sea el problema, después de lo cual devolveremos todo a su lugar.

Al actualizar la página en el navegador o volver a ejecutar nuestro comando curl, podrá ver la página de prueba de Django.

Entonces, después de asegurarnos de que todo funciona y no hay más problemas de permisos, habilitamos SELinux nuevamente.

sudo setenforce 1

No hablaré aquí sobre audit2allow o la creación de políticas basadas en alertas con sepolgen, ya que no existe una aplicación Django real en este momento, por lo que no hay un mapa completo de a qué Gunicorn podría querer acceder y a qué debería denegar el acceso. Por lo tanto, es necesario mantener SELinux en ejecución para proteger el sistema y, al mismo tiempo, permitir que la aplicación se ejecute y deje mensajes en el registro de auditoría para que luego se pueda crear la política real a partir de ellos.

Especificación de dominios permisivos

No todo el mundo ha oído hablar de los dominios permitidos en SELinux, pero no son nada nuevo. Muchos incluso trabajaron con ellos sin siquiera darse cuenta. Cuando se crea una política basada en mensajes de auditoría, la política creada representa el dominio resuelto. Intentemos crear una política de permisos simple.

Para crear un dominio permitido específico para Gunicorn, necesita algún tipo de política y también debe marcar los archivos apropiados. Además, se necesitan herramientas para elaborar nuevas políticas.

sudo yum install selinux-policy-devel

El mecanismo de dominios permitidos es una gran herramienta para identificar problemas, especialmente cuando se trata de una aplicación personalizada o aplicaciones que se envían sin políticas ya creadas. En este caso, la política de dominio permitido para Gunicorn será lo más simple posible: declarar un tipo principal (gunicorn_t), declarar un tipo que usaremos para marcar múltiples ejecutables (gunicorn_exec_t) y luego configurar una transición para que el sistema marque correctamente procesos en ejecución. La última línea establece la política como habilitada de forma predeterminada en el momento en que se 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;

Puede compilar este archivo de política y agregarlo a su sistema.

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

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

Comprobemos si SELinux está bloqueando algo más que a 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 evita que Nginx escriba datos en el socket UNIX utilizado por Gunicorn. Normalmente, en tales casos, las políticas comienzan a cambiar, pero aún quedan otros desafíos por delante. También puede cambiar la configuración del dominio de un dominio de restricción a un dominio de permiso. Ahora muevamos httpd_t al dominio de permisos. Esto le dará a Nginx el acceso necesario y podremos continuar con el trabajo de depuración.

sudo semanage permissive -a httpd_t

Entonces, una vez que haya logrado mantener SELinux protegido (realmente no debería dejar un proyecto SELinux en modo restringido) y los dominios de permiso estén cargados, necesita averiguar qué es exactamente lo que se debe marcar como gunicorn_exec_t para que todo funcione correctamente. de nuevo. Intentemos visitar el sitio web para ver nuevos mensajes sobre restricciones de acceso.

sudo ausearch -m AVC -c gunicorn

Verás muchos mensajes que contienen 'comm="gunicorn"' que hacen varias cosas en archivos en /srv/djangoapp, por lo que este es obviamente uno de los comandos que vale la pena marcar.

Pero además aparece un mensaje como este:

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 observa el estado del servicio gunicorn o ejecuta el comando ps, no verá ningún proceso en ejecución. Parece que gunicorn está intentando acceder al intérprete de Python en nuestro entorno virtualenv, posiblemente para ejecutar scripts de trabajo. Ahora marquemos estos dos archivos ejecutables y verifiquemos 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 antes de poder seleccionar la nueva etiqueta. Puede reiniciarlo inmediatamente o detener el servicio y dejar que el socket lo inicie cuando abra el sitio en el navegador. Verifique que los procesos hayan recibido las etiquetas correctas usando ps.

ps -efZ | grep gunicorn

¡No olvide crear una política SELinux normal más adelante!

Si observa 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 comprende qué tipo de acceso necesita una aplicación real, podrá encontrar rápidamente la mejor manera de resolver dichos problemas. Pero hasta entonces, es mejor mantener el sistema seguro y obtener una auditoría clara y utilizable del proyecto Django.

sudo ausearch -m AVC

¡Resultó!

Ha aparecido un proyecto Django en funcionamiento con una interfaz basada en Nginx y Gunicorn WSGI. Configuramos Python 3 y PostgreSQL 10 desde los repositorios RHEL 8 Beta. Ahora puede avanzar y crear (o simplemente implementar) aplicaciones Django o explorar otras herramientas disponibles en RHEL 8 Beta para automatizar el proceso de configuración, mejorar el rendimiento o incluso contener esta configuración.

Fuente: habr.com

Añadir un comentario