SELinux e AppArmor: quando o culpado é a proteção
A permissão está certa, o dono está certo, e a aplicação insiste em "permission denied". Existe uma terceira camada de controle que quase ninguém lembra — e desligá-la não é a resposta.
Equipe EasyOps Cloud · · 7 min de leitura
O arquivo pertence ao usuário certo. A permissão é 640 e o processo está no
grupo. Você conferiu três vezes. E a aplicação continua devolvendo Permission
denied.
Quando a permissão tradicional do Unix está claramente correta e o acesso ainda é negado, existe uma terceira camada agindo: o controle de acesso obrigatório. Em RHEL, AlmaLinux e Rocky, ele se chama SELinux; em Debian e Ubuntu, AppArmor.
A reação mais comum é desligá-lo. É compreensível e é a pior das saídas — essas camadas existem justamente para conter o que a permissão comum não contém, e são o que transforma uma falha de aplicação em incidente limitado.
Reconhecer que é isso
O sintoma característico: permissão correta, usuário correto, acesso negado. Antes de qualquer coisa, confirme qual sistema está ativo e em que modo.
# RHEL, AlmaLinux, Rocky
getenforce
sestatus
# Debian, Ubuntu
sudo aa-statusgetenforce responde Enforcing, Permissive ou Disabled. Em Permissive, ele
registra o que bloquearia mas deixa passar — o que é a chave do diagnóstico,
como veremos.
A confirmação definitiva vem do log:
# SELinux
sudo ausearch -m AVC -ts recent
sudo journalctl -t setroubleshoot --since '30 min ago'
# AppArmor
sudo journalctl -k --since '30 min ago' | grep -i apparmor
sudo dmesg | grep -i 'apparmor.*DENIED'Uma linha de negação do SELinux tem esta cara:
type=AVC msg=audit(...): avc: denied { write } for pid=2841
comm="nginx" name="cache" dev="vda1" ino=131079
scontext=system_u:system_r:httpd_t:s0
tcontext=unconfined_u:object_r:default_t:s0 tclass=dirTraduzindo: o processo no contexto httpd_t tentou escrever em um diretório cujo
contexto é default_t, e a política não permite. O problema não é permissão — é rótulo.
O modelo mental do SELinux
Todo processo e todo arquivo carregam um rótulo de contexto. A política define quais contextos podem interagir com quais. É isso.
ls -Z /var/www/meuapp/
ps -eZ | grep nginx
id -ZO erro mais comum acontece ao mover arquivo em vez de copiar, ou ao criar
diretório fora do lugar esperado. Um arquivo movido de /home para /var/www mantém
o rótulo de origem, e o servidor web não consegue lê-lo — mesmo com permissão 644
e dono correto.
A correção é restaurar o rótulo padrão do caminho:
sudo restorecon -Rv /var/www/meuapp/Esse comando resolve uma fatia enorme dos casos, e é o primeiro a tentar. Ele consulta a política e aplica o contexto que aquele caminho deveria ter.
Se o diretório está em local não padrão, é preciso ensinar a política primeiro:
sudo semanage fcontext -a -t httpd_sys_content_t "/opt/site(/.*)?"
sudo restorecon -Rv /opt/sitePara conteúdo que a aplicação precisa escrever, o tipo é outro:
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/opt/site/storage(/.*)?"
sudo restorecon -Rv /opt/site/storageRepare na distinção entre conteúdo de leitura e de escrita. Rotular tudo como gravável anula boa parte da proteção — a ideia é justamente que o código não seja gravável pelo processo que o executa, como discutido no artigo sobre usuários e permissões.
Booleanos: o ajuste que resolve sem afrouxar
Boa parte dos bloqueios do SELinux não é sobre arquivo, e sim sobre comportamento — o servidor web conectar em banco remoto, enviar e-mail, ou acessar a rede.
Esses casos têm chaves prontas:
getsebool -a | grep httpd | head -20# Permitir que o servidor web faça conexões de rede
sudo setsebool -P httpd_can_network_connect on
# Só para banco
sudo setsebool -P httpd_can_network_connect_db on
# Permitir envio de e-mail
sudo setsebool -P httpd_can_sendmail onO -P torna permanente. Sem ele, o ajuste some no próximo reinício — e o problema
volta semanas depois, sem que ninguém associe.
Procurar o booleano certo antes de escrever política customizada é quase sempre o caminho: eles são recortes estreitos, mantidos pela distribuição, e não abrem mais do que o necessário.
Diagnosticar em Permissive, corrigir em Enforcing
A técnica que resolve o caso difícil, sem deixar a máquina desprotegida permanentemente.
Coloque em modo permissivo, exercite a aplicação por completo, e colete tudo que teria sido bloqueado:
sudo setenforce 0
# exercite a aplicação: upload, envio de e-mail, geração de relatório
sudo ausearch -m AVC -ts recent | audit2allow -m meuappA saída é uma política legível, mostrando exatamente o que faltou. Leia antes de aplicar — é aqui que se decide se aquilo é legítimo ou se a aplicação está tentando algo que não deveria.
sudo ausearch -m AVC -ts recent | audit2allow -M meuapp
sudo semodule -i meuapp.pp
sudo setenforce 1Duas advertências. O audit2allow gera permissões para tudo que apareceu,
inclusive o que era bug — revise em vez de aplicar cegamente. E prefira restorecon
ou booleano quando eles resolverem: política customizada é mais uma coisa para
manter.
O modo permissivo deve durar minutos, não dias. Deixar assim "até resolver" é desligar a proteção com passos extras.
AppArmor, que é mais simples
O AppArmor trabalha por caminho em vez de rótulo, o que o torna mais fácil de ler.
sudo aa-status
ls /etc/apparmor.d/Cada perfil lista os caminhos que o programa pode acessar e com quais permissões. As negações aparecem no log do kernel, indicando o perfil e o arquivo:
sudo journalctl -k | grep 'apparmor="DENIED"' | tailPara colocar um perfil específico em modo de aprendizado, sem afetar os demais:
sudo aa-complain /etc/apparmor.d/usr.sbin.nginx
# exercite a aplicação
sudo aa-logprof # revisa e sugere ajustes
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginxO aa-logprof é interativo e pergunta item a item — mais trabalhoso que o audit2allow
e mais seguro, porque força a leitura de cada permissão concedida.
Editando à mão, a sintaxe é direta:
# /etc/apparmor.d/local/usr.sbin.nginx
/opt/site/** r,
/opt/site/storage/** rw,sudo systemctl reload apparmorOnde isso costuma morder
Os cenários que geram mais chamados, em ordem:
- Porta não padrão. Rodar o servidor web em 8080 é bloqueado até a porta ser
declarada:
sudo semanage port -a -t http_port_t -p tcp 8080. - Diretório de aplicação fora de `/var/www`. Precisa de rótulo ou perfil.
- Arquivo movido em vez de copiado, mantendo o rótulo antigo.
- Backup restaurado sem preservar contexto —
restorecon -Rresolve. - Container com volume do host. O SELinux exige o sufixo
:zou:Zna montagem, senão o processo dentro não acessa.
Esse último aparece muito e tem correção de um caractere:
docker run -v /opt/dados:/dados:Z minha-imagemPor que não desligar
Vale ser direto sobre o que se perde. Essas camadas contêm exatamente o cenário que mais importa: a aplicação foi comprometida e o atacante tenta ir além dela. Sem elas, um processo comprometido faz tudo que o usuário dele pode fazer; com elas, ele faz apenas o que a política prevê para aquele papel.
Em servidor exposto à internet, essa diferença é a barreira entre um site comprometido e uma máquina comprometida.
Se ainda assim for necessário desligar temporariamente para destravar produção, faça-o com prazo e com registro — e trate como pendência, não como solução. É a mesma disciplina descrita no artigo sobre o que fazer quando não dá para atualizar: exceção consciente e documentada é defensável; exceção esquecida vira o buraco que ninguém lembra que abriu.
Um roteiro de dez minutos
Quando aparecer um Permission denied inexplicável, esta sequência resolve a
maioria dos casos sem improviso:
# 1. É o MAC mesmo?
getenforce 2>/dev/null; sudo aa-status 2>/dev/null | head -3
# 2. O que foi negado, exatamente
sudo ausearch -m AVC -ts recent 2>/dev/null | tail -20
sudo journalctl -k --since '15 min ago' | grep -i 'denied'
# 3. Rótulo do arquivo versus rótulo esperado
ls -Z /caminho/do/arquivo
matchpathcon /caminho/do/arquivo # o que a política espera
# 4. A correção mais provável
sudo restorecon -Rv /caminho/O passo 3 é o que dá a resposta na maior parte dos casos: o matchpathcon mostra
lado a lado o contexto atual e o esperado, e a divergência entre os dois é o
problema inteiro.
Se restorecon não resolver, a pergunta seguinte é se o comportamento — não o
arquivo — está sendo bloqueado. Aí o caminho é a lista de booleanos, e só depois
a política customizada.
Uma observação sobre expectativa: essas camadas parecem hostis no começo porque falham de forma opaca, sem dizer o que fazer. Depois de aprender a ler a negação, elas ficam previsíveis — e a mesma opacidade que irrita durante a configuração é o que impede um invasor de descobrir o que a política permite.
Vale uma nota sobre distribuições. Ao migrar de CentOS para Debian ou Ubuntu —
ou o contrário —, muda também o sistema de controle obrigatório, e com ele todo
o vocabulário de diagnóstico. Quem vinha de restorecon e booleanos passa a lidar
com perfis por caminho, e vice-versa. É um custo de transição real e raramente
mencionado nos guias de migração, como o de para onde ir depois do CentOS.