Workshop RHEL 8 Beta: Construindo aplicações web funcionais

RHEL 8 Beta oferece aos desenvolvedores muitos recursos novos, cuja listagem pode levar páginas, no entanto, aprender coisas novas é sempre melhor na prática, então a seguir oferecemos um workshop sobre como realmente criar uma infraestrutura de aplicativos baseada no Red Hat Enterprise Linux 8 Beta.

Workshop RHEL 8 Beta: Construindo aplicações web funcionais

Vamos tomar como base o Python, uma linguagem de programação popular entre os desenvolvedores, uma combinação de Django e PostgreSQL, uma combinação bastante comum para a criação de aplicativos, e configurar o RHEL 8 Beta para trabalhar com eles. Em seguida, adicionaremos mais alguns ingredientes (não classificados).

O ambiente de testes vai mudar, pois é interessante explorar as possibilidades de automação, trabalhando com containers e experimentando ambientes com múltiplos servidores. Para começar um novo projeto, você pode começar criando um protótipo pequeno e simples manualmente para ver exatamente o que precisa acontecer e como ele interage, e então prosseguir para automatizar e criar configurações mais complexas. Hoje estamos falando sobre a criação desse protótipo.

Vamos começar implantando a imagem RHEL 8 Beta VM. Você pode instalar uma máquina virtual do zero ou usar a imagem de convidado KVM disponível com sua assinatura Beta. Ao usar uma imagem convidada, você precisará configurar um CD virtual que conterá metadados e dados do usuário para inicialização da nuvem (cloud-init). Você não precisa fazer nada de especial com a estrutura do disco ou com os pacotes disponíveis; qualquer configuração serve.

Vamos dar uma olhada em todo o processo.

Instalando Django

Com a versão mais recente do Django, você precisará de um ambiente virtual (virtualenv) com Python 3.5 ou posterior. Nas notas do Beta você pode ver que o Python 3.6 está disponível, vamos verificar se esse é realmente o caso:

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

A Red Hat usa ativamente Python como um kit de ferramentas de sistema no RHEL, então por que isso acontece?

O fato é que muitos desenvolvedores Python ainda estão contemplando a transição do Python 2 para o Python 2, enquanto o próprio Python 3 está em desenvolvimento ativo e cada vez mais novas versões aparecem constantemente. Portanto, para atender à necessidade de ferramentas de sistema estáveis ​​e, ao mesmo tempo, oferecer aos usuários acesso a várias novas versões do Python, o Python do sistema foi movido para um novo pacote e forneceu a capacidade de instalar o Python 2.7 e 3.6. Mais informações sobre as alterações e por que foram feitas podem ser encontradas na publicação em Blog de Langdon White (Langdon Branco).

Portanto, para fazer o Python funcionar, você só precisa instalar dois pacotes, com python3-pip incluído como dependência.

sudo yum install python36 python3-virtualenv

Por que não usar chamadas diretas de módulo como Langdon sugere e instalar o pip3? Tendo em mente a próxima automação, sabe-se que o Ansible exigirá a instalação do pip para funcionar, uma vez que o módulo pip não suporta virtualenvs com um executável pip personalizado.

Com um interpretador python3 funcional à sua disposição, você pode continuar com o processo de instalação do Django e ter um sistema funcionando junto com nossos outros componentes. Existem muitas opções de implementação disponíveis na Internet. Há uma versão apresentada aqui, mas os usuários podem usar seus próprios processos.

Instalaremos as versões PostgreSQL e Nginx disponíveis no RHEL 8 por padrão usando Yum.

sudo yum install nginx postgresql-server

O PostgreSQL exigirá o psycopg2, mas ele precisa estar disponível apenas em um ambiente virtualenv, então iremos instalá-lo usando pip3 junto com Django e Gunicorn. Mas primeiro precisamos configurar o virtualenv.

Sempre há muito debate sobre a escolha do local certo para instalar projetos Django, mas em caso de dúvida, você sempre pode recorrer ao Linux Filesystem Hierarchy Standard. Especificamente, o FHS diz que /srv é usado para: “armazenar dados específicos do host – dados que o sistema produz, como dados e scripts do servidor web, dados armazenados em servidores FTP e controlar repositórios do sistema”. -2.3 em 2004)."

Este é exatamente o nosso caso, então colocamos tudo o que precisamos em /srv, que pertence ao usuário da nossa aplicação (usuário da nuvem).

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 o PostgreSQL e o Django é fácil: crie um banco de dados, crie um usuário, configure permissões. Uma coisa a ter em mente ao instalar inicialmente o PostgreSQL é o script postgresql-setup que é instalado com o pacote postgresql-server. Este script ajuda a executar tarefas básicas associadas à administração do cluster de banco de dados, como inicialização do cluster ou processo de atualização. Para configurar uma nova instância PostgreSQL em um sistema RHEL, precisamos executar o comando:

sudo /usr/bin/postgresql-setup -initdb

Você pode então iniciar o PostgreSQL usando systemd, criar um banco de dados e configurar um projeto no Django. Lembre-se de reiniciar o PostgreSQL após fazer alterações no arquivo de configuração de autenticação do cliente (geralmente pg_hba.conf) para configurar o armazenamento de senha para o usuário do aplicativo. Se você encontrar outras dificuldades, certifique-se de alterar as configurações de IPv4 e IPv6 no arquivo 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

No arquivo /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

No arquivo /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 }}',
   }
}

Depois de definir o arquivo settings.py no projeto e definir a configuração do banco de dados, você pode iniciar o servidor de desenvolvimento para garantir que tudo funcione. Após iniciar o servidor de desenvolvimento, é uma boa ideia criar um usuário administrador para testar a conexão com o banco de dados.

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

WSGI? Ei?

O servidor de desenvolvimento é útil para testes, mas para executar o aplicativo você deve configurar o servidor e proxy apropriados para o Web Server Gateway Interface (WSGI). Existem várias combinações comuns, por exemplo, Apache HTTPD com uWSGI ou Nginx com Gunicorn.

A função da Web Server Gateway Interface é encaminhar solicitações do servidor web para a estrutura web Python. WSGI é uma relíquia do passado terrível, quando os motores CGI existiam, e hoje o WSGI é o padrão de fato, independentemente do servidor web ou da estrutura Python usada. Mas, apesar de seu uso generalizado, ainda existem muitas nuances ao trabalhar com essas estruturas e muitas opções. Neste caso, tentaremos estabelecer a interação entre Gunicorn e Nginx através de um soquete.

Como esses dois componentes estão instalados no mesmo servidor, vamos tentar usar um soquete UNIX em vez de um soquete de rede. Como a comunicação requer um soquete em qualquer caso, vamos tentar dar mais um passo e configurar a ativação do soquete para Gunicorn via systemd.

O processo de criação de serviços ativados por soquete é bastante simples. Primeiro, é criado um arquivo de unidade que contém uma diretiva ListenStream apontando para o ponto em que o soquete UNIX será criado e, em seguida, um arquivo de unidade para o serviço no qual a diretiva Requires apontará para o arquivo de unidade de soquete. Então, no arquivo da unidade de serviço, tudo o que resta é chamar o Gunicorn do ambiente virtual e criar uma ligação WSGI para o soquete UNIX e o aplicativo Django.

Aqui estão alguns exemplos de arquivos de unidade que você pode usar como base. Primeiro configuramos o soquete.

[Unit]
Description=Gunicorn WSGI socket

[Socket]
ListenStream=/run/gunicorn.sock

[Install]
WantedBy=sockets.target

Agora você precisa configurar o daemon 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 o Nginx, é uma simples questão de criar arquivos de configuração de proxy e configurar um diretório para armazenar conteúdo estático, se você estiver usando um. No RHEL, os arquivos de configuração do Nginx estão localizados em /etc/nginx/conf.d. Você pode copiar o exemplo a seguir no arquivo /etc/nginx/conf.d/default.conf e iniciar o serviço. Certifique-se de definir server_name para corresponder ao seu nome 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 o soquete Gunicorn e Nginx usando systemd e você estará pronto para iniciar os testes.

Erro de gateway incorreto?

Se você inserir o endereço em seu navegador, provavelmente receberá um erro 502 Bad Gateway. Isso pode ser causado por permissões de soquete UNIX configuradas incorretamente ou por problemas mais complexos relacionados ao controle de acesso no SELinux.

No log de erros do nginx você pode ver uma linha 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"

Se testarmos o Gunicorn diretamente, obteremos uma resposta vazia.

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

Vamos descobrir por que isso acontece. Se você abrir o log, provavelmente verá que o problema está relacionado ao SELinux. Como estamos executando um daemon para o qual nenhuma política foi criada, ele é marcado como init_t. Vamos testar essa teoria na prática.

sudo setenforce 0

Tudo isso pode causar críticas e lágrimas de sangue, mas isso é apenas uma depuração do protótipo. Vamos desabilitar a verificação apenas para ter certeza de que esse é o problema, após o que retornaremos tudo ao seu lugar.

Ao atualizar a página no navegador ou executar novamente nosso comando curl, você poderá ver a página de teste do Django.

Assim, tendo certeza de que tudo funciona e que não há mais problemas de permissão, habilitamos o SELinux novamente.

sudo setenforce 1

Não falarei sobre audit2allow ou criação de políticas baseadas em alertas com sepolgen aqui, já que não há nenhuma aplicação Django real no momento, então não há um mapa completo do que o Gunicorn pode querer acessar e ao que ele deve negar acesso. Portanto, é necessário manter o SELinux em execução para proteger o sistema, ao mesmo tempo em que permite que o aplicativo seja executado e deixe mensagens no log de auditoria para que a política real possa ser criada a partir delas.

Especificando domínios permissivos

Nem todo mundo já ouviu falar de domínios permitidos no SELinux, mas eles não são novidade. Muitos até trabalharam com eles sem perceber. Quando uma política é criada com base em mensagens de auditoria, a política criada representa o domínio resolvido. Vamos tentar criar uma política de licenciamento simples.

Para criar um domínio permitido específico para Gunicorn, você precisa de algum tipo de política e também de marcar os arquivos apropriados. Além disso, são necessárias ferramentas para montar novas políticas.

sudo yum install selinux-policy-devel

O mecanismo de domínios permitidos é uma ótima ferramenta para identificar problemas, especialmente quando se trata de um aplicativo personalizado ou de aplicativos enviados sem políticas já criadas. Neste caso, a política de domínio permitido para Gunicorn será a mais simples possível - declarar um tipo principal (gunicorn_t), declarar um tipo que usaremos para marcar vários executáveis ​​(gunicorn_exec_t) e, em seguida, configurar uma transição para o sistema marcar corretamente processos em execução . A última linha define a política como habilitada por padrão no momento em que é carregada.

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;

Você pode compilar esse arquivo de política e adicioná-lo ao seu sistema.

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

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

Vamos verificar se o SELinux está bloqueando algo diferente do que nosso daemon desconhecido está acessando.

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 o Nginx grave dados no soquete UNIX usado pelo Gunicorn. Normalmente, nesses casos, as políticas começam a mudar, mas há outros desafios pela frente. Você também pode alterar as configurações de domínio de um domínio de restrição para um domínio de permissão. Agora vamos mover o httpd_t para o domínio de permissões. Isso dará ao Nginx o acesso necessário e poderemos continuar com o trabalho de depuração.

sudo semanage permissive -a httpd_t

Então, uma vez que você conseguiu manter o SELinux protegido (você realmente não deveria deixar um projeto SELinux em modo restrito) e os domínios de permissão foram carregados, você precisa descobrir o que exatamente precisa ser marcado como gunicorn_exec_t para que tudo funcione corretamente de novo. Vamos tentar visitar o site para ver novas mensagens sobre restrições de acesso.

sudo ausearch -m AVC -c gunicorn

Você verá muitas mensagens contendo 'comm="gunicorn"' que fazem várias coisas em arquivos em /srv/djangoapp, então este é obviamente um dos comandos que vale a pena sinalizar.

Mas além disso, aparece uma mensagem como esta:

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 você observar o status do serviço gunicorn ou executar o comando ps, não verá nenhum processo em execução. Parece que o gunicorn está tentando acessar o interpretador Python em nosso ambiente virtualenv, possivelmente para executar scripts de trabalho. Então agora vamos marcar esses dois arquivos executáveis ​​e verificar se podemos abrir nossa página de testes do Django.

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

O serviço gunicorn precisará ser reiniciado antes que a nova tag possa ser selecionada. Você pode reiniciá-lo imediatamente ou interromper o serviço e deixar o soquete iniciá-lo ao abrir o site no navegador. Verifique se os processos receberam os rótulos corretos usando ps.

ps -efZ | grep gunicorn

Não se esqueça de criar uma política SELinux normal mais tarde!

Se você observar as mensagens AVC agora, a última mensagem contém permissive=1 para tudo relacionado ao aplicativo e permissive=0 para o resto do sistema. Se você entender que tipo de acesso um aplicativo real precisa, poderá encontrar rapidamente a melhor maneira de resolver esses problemas. Mas até então, é melhor manter o sistema seguro e obter uma auditoria clara e utilizável do projeto Django.

sudo ausearch -m AVC

Acabou!

Um projeto Django funcional apareceu com um frontend baseado em Nginx e Gunicorn WSGI. Configuramos Python 3 e PostgreSQL 10 a partir dos repositórios RHEL 8 Beta. Agora você pode avançar e criar (ou simplesmente implantar) aplicativos Django ou explorar outras ferramentas disponíveis no RHEL 8 Beta para automatizar o processo de configuração, melhorar o desempenho ou até mesmo conteinerizar essa configuração.

Fonte: habr.com

Adicionar um comentário