SELinux sem Medo: Politicas Customizadas para Servicos Criticos
Da auditoria com audit2allow a modulos de policy versionados e mantidos em producao, sem cair no permissivo eterno.

Neste artigo
Toda vez que um servico quebra em RHEL ou Rocky, o reflexo do time de plantao e o mesmo: setenforce 0, problema resolvido, ticket fechado. Seis meses depois o cluster inteiro roda em permissive, ninguem lembra por que, e o relatorio de compliance vira ficcao cientifica. A equipe Basilisk OffSec passou dois anos derrubando ambientes assim em red teams autorizados, e a conclusao e direta: um SELinux desativado e um dos caminhos mais confiaveis de uma unica RCE ate o comprometimento total. Este post mostra como escrever policies customizadas para servicos criticos sem quebrar a producao, e por que setenforce 0 nao e uma correcao e sim uma conta adiada.
Enforcing sobre permissive: por que importa#
SELinux e Mandatory Access Control: mesmo quando um processo roda como root, a policy limita quais tipos ele pode ler, escrever e executar. E exatamente isso que quebra uma cadeia de exploit, porque um httpd_t comprometido simplesmente nao pode ler shadow_t nem escrever em bin_t. Permissive so registra, nao bloqueia nada, entao um cluster em permissive esta funcionalmente desprotegido. O estado alvo e sempre Enforcing, verificavel com getenforce e sestatus. Um host enforcing bem mantido nao e obstaculo para a operacao, e a ultima defesa de perimetro quando uma vulnerabilidade de aplicacao e explorada; complementa o trabalho base de Hardening de Linux Servidor: CIS Benchmark Aplicado sem Quebrar Producao.
Ler a policy existente antes de escrever#
Antes de escrever qualquer policy, voce precisa ler o que ja existe. O comando seinfo -t lista cerca de 5.000 tipos no RHEL 9 padrao, e sesearch --allow -s httpd_t mostra exatamente o que o Apache pode tocar. seinfo -ahttpd_t -x resolve os atributos de um dominio, e sesearch --allow -s httpd_t -t etc_t -c file responde com precisao se um acesso ja esta permitido. Comecamos toda investigacao com essas consultas, porque em 80% dos casos ja existe um tipo ou boolean adequado, e voce nao precisa escrever uma policy nova, so corrigir o label ou acionar o switch.
Capturar os AVC de forma limpa#
Quando algo realmente falta, voce captura os denials em vez de adivinhar. Rode o servico com ausearch -m AVC -ts recent em paralelo e ponha o dominio afetado em modo permissivo TEMPORARIO, jamais o sistema inteiro: semanage permissive -a httpd_t. Assim so esse servico roda sem travas e registra cada violacao, enquanto o resto do sistema fica enforcing. Reproduza o caso de uso completo (start, reload, todos os codepaths), junte os AVC e remova a excecao logo depois com semanage permissive -d httpd_t. Uma entrada permissive esquecida e tao perigosa quanto um SELinux desativado globalmente.
audit2allow e uma faca de dois gumes#
O audit2allow e uma faca de dois gumes. Rodar ausearch -m AVC | audit2allow -M meumodulo gera um .te que compila e funciona, mas frequentemente concede permissoes absurdas tipo allow httpd_t shadow_t:file read. Nosso checklist interno exige que todo .te passe por revisao manual antes do semodule -i. Procure por regras que toquem em shadow_t, etc_t, kernel_t ou self:capability sys_admin, esses sao sinais de alerta. Permitir um denial porque o servico nao sobe e comodo e muitas vezes a origem exata da brecha que um atacante vai precisar depois. Em cada regra pergunte: por que o processo quer isso, e o acesso e realmente necessario?
Policy do zero com refpolicy#
Para servicos novos, preferimos escrever policy do zero com a macro language do refpolicy. Um modulo tipico tem tres arquivos: meuservico.te com as regras, meuservico.fc com file contexts e meuservico.if com interfaces para outros dominios. make -f /usr/share/selinux/devel/Makefile gera o .pp que voce instala com semodule -i. Versionamos esses tres arquivos no git junto com Ansible, e cada PR passa pela mesma revisao que o codigo de aplicacao. Assim a policy fica rastreavel, reproduzivel e auditavel, em vez de apodrecer como trabalho manual sem documentacao num unico host.
Portas e file contexts: 80% dos casos#
Servicos que abrem socket em portas nao padrao sao o caso mais comum de quebra silenciosa. Postgres na 5433 por exemplo precisa de semanage port -a -t postgresql_port_t -p tcp 5433, e nao de uma nova policy. Nginx servindo arquivos fora de /var/www quer semanage fcontext -a -t httpd_sys_content_t "/srv/app(/.*)?" seguido de restorecon -Rv /srv/app. 80 por cento dos casos que vemos sao problemas de label e porta, nao regras allow faltando. Por isso confira sempre primeiro com ls -Z e semanage port -l se um label errado ou uma porta nao registrada e a causa, antes de sequer pensar num .te.
Booleans em vez de policy custom#
Muitos requisitos aparentemente complexos ja estao cobertos por um boolean. getsebool -a | grep httpd mostra dezenas de switches; httpd_can_network_connect permite conexoes de saida, httpd_can_network_connect_db so para o banco. Fixe-os de forma persistente com setsebool -P httpd_can_network_connect_db on. Um boolean e sempre preferivel a um modulo custom, porque e mantido pelos empacotadores da distro, documentado e carregado nos updates. Policy custom e o ultimo recurso, nao o primeiro impulso; quem procura booleans primeiro escreve muito menos arquivos .te proprios na pratica.
Confined vs. unconfined: a falacia comum#
Um erro muito difundido e achar que um servico esta protegido so porque o SELinux esta enforcing. Muitos processos auto-iniciados rodam como unconfined_service_t ou init_t e estao praticamente sem freio. Verifique com ps -eZ | grep meuservico em qual dominio o servico realmente roda. Um binario sob /usr/local/bin costuma carregar bin_t em vez de um dominio proprio, entao nao ocorre transicao. Todo o esforco de uma policy custom nao vale nada se o processo nunca transiciona para o dominio confined; e pra isso que existe justamente o arquivo .fc mais uma type_transition a partir do init_t que o inicia. Verifique sempre a transicao em vez de assumi-la.
Testar e fazer rollback#
Uma policy e codigo e se testa como codigo. Instale primeiro em staging com semodule -i meuservico.pp, exercite o caso de uso completo e cheque ausearch -m AVC -ts recent buscando zero denials novos. Liste os modulos carregados com semodule -l, e remova um defeituoso na hora com semodule -r meuservico. Tenha em mente o mecanismo de prioridades: semodule -X 400 -i carrega com prioridade mais alta e sobrescreve a versao da distro de forma controlada. Para iterar rapido, ponha brevemente o dominio alvo em permissive, junte os AVC restantes numa unica passada e adicione-os com justificativa, em vez de cair em dez rodadas de deploy-e-reza.
Observabilidade e policy drift#
Manutencao em producao exige observabilidade. Configuramos setroubleshoot-server em modo silent enviando AVC para o SIEM via journald, com regras Sigma especificas para denials nao esperados. Quando um deploy quebra, o alerta chega antes do usuario reclamar. Tambem rodamos sealert -a /var/log/audit/audit.log semanalmente em staging para pegar policy drift antes que chegue a producao. Para o lado forense, quando um denial aponta para um ataque real, engata DFIR no Linux: Triagem ao Vivo com UAC e Velociraptor, e a policy assinada encaixa na cadeia de Supply Chain Security: Assinatura com Sigstore e SBOM Real em CI/CD.
Pegadinhas#
As pegadinhas mais comuns: setenforce 0 como estado permanente em vez de diagnostico de 15 minutos; usar chcon no lugar de semanage fcontext mais restorecon, o que perde o label no proximo restorecon ou relabel; aplicar a saida do audit2allow as cegas; e esquecer as regras dontaudit que escondem denials relevantes (desligadas temporariamente com semodule -DB). Outro classico e um label perdido num restore com tar; use tar --selinux ou relabele explicitamente depois. A base de recon pela perspectiva do atacante esta em Nmap Avancado: Scripts NSE para Recon Interno em Lab Corporativo Simulado.
Checklist#
Antes de uma policy custom ir para producao: (1) procurou primeiro por um tipo e boolean adequados; (2) AVC capturados so em permissive por dominio, o sistema ficou enforcing; (3) cada .te revisado manualmente, nenhuma regra shadow_t/sys_admin sem justificativa; (4) file contexts via semanage fcontext mais restorecon, nunca chcon; (5) portas registradas; (6) tres arquivos versionados no git; (7) .pp assinado e deployado via Ansible; (8) alerta SIEM sobre denials inesperados ativo; (9) enforcing confirmado via getenforce; (10) nenhuma entrada permissive deixada para tras.
FAQ#
Por que nao trocar SELinux por AppArmor? Na familia RHEL, SELinux e o caminho suportado e integrado; trocar joga fora as policies da distro. Enforcing custa desempenho? O overhead fica em um digito percentual e e desprezivel na pratica diante do ganho de seguranca. E um servico que nao sobe de jeito nenhum? Ponha permissive por dominio, reproduza o fluxo completo, junte AVC, escreva a policy, revise, volte para enforcing, verifique. Nunca deixe o sistema inteiro em permissive.
Takeaway pratico: nunca rode setenforce 0 em producao por mais de 15 minutos. Use semanage permissive -a dominio_t para isolar o problema, capture AVC com ausearch, revise a saida do audit2allow manualmente, commite o .te no git, assine o .pp e deploye via Ansible. Se voce nao consegue justificar cada allow rule em revisao de codigo, a policy nao esta pronta. SELinux nao e obstaculo, e a ultima defesa de perimetro entre uma unica vulnerabilidade e a perda total do host.