Hardening com SELinux no RHEL 10, Rocky Linux e AlmaLinux: Guia Prático com Políticas Personalizadas em 2026

Guia prático de hardening com SELinux no RHEL 10, Rocky Linux e AlmaLinux em 2026: política targeted, booleans, contextos com semanage e restorecon, módulos customizados com audit2allow, isolamento de contêineres Podman com MCS, e troubleshooting via sealert.

Hardening SELinux RHEL 10: Guia 2026

Atualizado: 22 de junho de 2026

O hardening com SELinux no RHEL 10, Rocky Linux 10 e AlmaLinux 10 consiste em manter a política targeted em modo enforcing, ajustar booleans para o seu workload, redefinir contextos de arquivo com semanage fcontext e restorecon, e empacotar exceções em módulos de política personalizados gerados a partir de logs AVC, em vez de desabilitar o subsistema. Na prática, é a diferença entre confinar um processo comprometido ao seu próprio diretório ou deixar a blast radius escapar para todo o filesystem. Este guia mostra como tratar o SELinux como uma camada arquitetural, não como um obstáculo a ser silenciado.

Confesso uma coisa antes de começar: passei boa parte da minha primeira década como SRE rodando setenforce 0 no susto. Foi só depois de investigar um incidente onde o SELinux teria bloqueado o que aconteceu que parei de tratá-lo como inimigo.

  • RHEL 10, Rocky Linux 10 e AlmaLinux 10 mantêm a política targeted como padrão, com setools-console e policycoreutils-python-utils como ferramentas de primeira linha para auditoria.
  • Nunca desabilite o SELinux para "fazer a aplicação funcionar". Mude para permissive, colete os AVCs e gere um módulo de política com audit2allow.
  • Contextos errados de arquivo são responsáveis pela maioria dos falsos positivos: semanage fcontext -a seguido de restorecon -Rv resolve 80% dos incidentes.
  • Booleans permitem habilitar comportamentos seguros (ex.: httpd_can_network_connect) sem reescrever política, e persistem com a flag -P.
  • Em contêineres, container_t e a categoria MCS isolam cada Pod, mas exigem rótulos corretos nos volumes (:Z ou :z no Podman) para não quebrar.
  • O troubleshooting eficaz começa em /var/log/audit/audit.log com ausearch -m AVC -ts recent e termina com sealert traduzindo o evento para humanos.

O que é o SELinux e por que ele continua relevante em 2026

O SELinux (Security-Enhanced Linux) é uma implementação de Mandatory Access Control (MAC) no kernel Linux que rotula cada processo, arquivo, socket e porta com um contexto de segurança, e decide cada acesso comparando esse contexto com uma política compilada em memória. Diferente do modelo DAC (proprietário e permissões), a política do SELinux não pode ser sobrescrita pelo dono do arquivo nem pelo próprio processo. Ela é aplicada pelo kernel antes que open(), execve() ou bind() retornem ao userland.

Em 2026, esse modelo continua relevante porque a maioria dos incidentes em servidores Linux não começa com escalada de privilégio direta para root. Começa com um worker do Nginx ou um runtime de contêiner sendo enganado para ler arquivos fora do seu escopo. Quando o SELinux está em enforcing com política targeted, esse worker tem um type (httpd_t) que não pode ler /etc/shadow nem se conectar a portas arbitrárias, independentemente de o processo rodar como root ou de o arquivo ter permissão 644. A blast radius do comprometimento fica contida ao domínio do serviço, e é exatamente esse comportamento que justifica manter a complexidade.

RHEL 10, Rocky Linux 10 e AlmaLinux 10, lançados em 2025 com kernel 6.12 LTS, trouxeram melhorias importantes: melhor performance do AVC cache, suporte ampliado a userspace_initial_context em systemd, e novos types para workloads de contêineres rootless. A documentação oficial do RHEL 10 sobre SELinux é a referência canônica para mudanças entre versões.

Verificar o status do SELinux e entender os modos

O primeiro comando em qualquer servidor recém-provisionado é sestatus. Ele resume modo atual, modo configurado em disco, política carregada e versão de policy. Em um RHEL 10 padrão você verá algo assim:

# sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Memory protection checking:     actual (secure)
Max kernel policy version:      33

Os três modos são:

  • enforcing: a política nega acessos não autorizados e registra cada negação como evento AVC. Este é o único modo aceitável em produção.
  • permissive: nada é negado, mas todas as violações são logadas. Útil para coletar AVCs ao introduzir um serviço novo sem quebrar o ambiente.
  • disabled: o subsistema não é carregado. A partir do RHEL 9, desabilitar via /etc/selinux/config está deprecated. A forma correta é o parâmetro de kernel selinux=0, e fortemente desencorajado fora de bare metal de troubleshooting.

Para alternar temporariamente entre enforcing e permissive sem reboot:

# setenforce 0    # vira permissive
# setenforce 1    # volta para enforcing
# getenforce
Enforcing

Política targeted vs MLS: qual escolher no RHEL 10

O RHEL 10 entrega três políticas no pacote selinux-policy-*: targeted (padrão), mls (Multi-Level Security) e minimum. A diferença não é cosmética: define quais domínios são confinados.

Característicatargetedmlsminimum
Domínios confinadosServiços de rede comuns (httpd, sshd, dns, ntpd...)Todos, com classificações Bell-LaPadulaApenas alvos selecionados pelo administrador
Usuários não-confinadosSim (unconfined_t)NãoSim
Caso de usoServidores e workstations geraisGoverno, defesa, ambientes classificadosEmbarcados e troubleshooting
Overhead de gestãoBaixoAlto, exige role engineeringMínimo
Compatível com MCS p/ contêineresSimSimLimitado

Para 99% dos workloads (incluindo Kubernetes, Podman, bancos de dados e clusters HPC) a política targeted é a escolha correta. Ela confina os serviços expostos enquanto deixa shells interativos em unconfined_t, evitando que cada SRE precise virar policy engineer só para reiniciar o serviço. Reserve mls para ambientes onde a classificação de dados (TOP SECRET, SECRET, CONFIDENTIAL) é um requisito legal, e nesse caso planeje semanas de modelagem de papéis, não horas.

Para verificar e trocar a política:

# cat /etc/selinux/config | grep -E '^(SELINUX|SELINUXTYPE)='
SELINUX=enforcing
SELINUXTYPE=targeted

# Mudar para mls (exemplo, exige relabel completo no próximo boot)
# sed -i 's/^SELINUXTYPE=.*/SELINUXTYPE=mls/' /etc/selinux/config
# touch /.autorelabel && reboot

Contextos de arquivo: semanage fcontext e restorecon na prática

A causa mais comum de "minha aplicação não funciona com SELinux" é simples: arquivos novos foram criados em diretórios não-padrão e herdaram o contexto errado. A política targeted espera que conteúdo web esteja em /var/www com type httpd_sys_content_t. Se você serve a partir de /srv/sites, o Nginx vai bater em AVC denied no primeiro request. (Eu já perdi uma tarde inteira nisso antes de aprender a olhar ls -lZ primeiro.)

Inspecionar o contexto atual:

# ls -lZ /srv/sites/
drwxr-xr-x. 2 nginx nginx unconfined_u:object_r:var_t:s0 4096 jun 22 14:01 app
# ps -eZ | grep nginx
system_u:system_r:httpd_t:s0 1234 ? 00:00:00 nginx

Aqui o processo está em httpd_t e os arquivos em var_t, e a política não permite essa combinação para leitura. A correção tem dois passos: registrar a regra persistente e aplicar nos arquivos existentes.

# Registrar a regra (sobrevive a relabel)
# semanage fcontext -a -t httpd_sys_content_t "/srv/sites(/.*)?"

# Aplicar nos arquivos atuais
# restorecon -Rv /srv/sites
Relabeled /srv/sites/app from unconfined_u:object_r:var_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0
...

# Confirmar
# matchpathcon /srv/sites/app/index.html
/srv/sites/app/index.html  system_u:object_r:httpd_sys_content_t:s0

Para portas (ex.: rodar httpd em 8443):

# semanage port -a -t http_port_t -p tcp 8443
# semanage port -l | grep http_port_t

Booleans SELinux: ajuste fino sem reescrever política

Booleans são chaves binárias dentro da política targeted que ligam ou desligam comportamentos opcionais. Em vez de reescrever regras quando o Apache precisa fazer requisições saindo do servidor para uma API externa, você liga um boolean. Honestamente, é o caminho mais subutilizado da stack.

# Listar booleans relacionados ao httpd
# getsebool -a | grep httpd
httpd_can_network_connect --> off
httpd_can_network_connect_db --> off
httpd_can_sendmail --> off
httpd_enable_homedirs --> off
httpd_use_nfs --> off
...

# Habilitar persistente (sobrevive a reboot e a reload de policy)
# setsebool -P httpd_can_network_connect on

# Confirmar
# getsebool httpd_can_network_connect
httpd_can_network_connect --> on

Sempre prefira um boolean a uma regra customizada quando o comportamento desejado já está modelado upstream. Os mais usados em 2026 incluem container_manage_cgroup, virt_sandbox_use_all_caps, nis_enabled, ssh_keysign e selinuxuser_execmod. Para descobrir o que cada boolean faz:

# semanage boolean -l | grep httpd_can_network_connect
httpd_can_network_connect      (off  ,  off)  Allow HTTPD scripts and modules to connect to the network using TCP.

A flag -P em setsebool é não-negociável em produção. Sem ela, sua mudança evapora no próximo reboot e você acorda com um pager às 3 da manhã (já vivi essa, recomendo evitar). Para auditoria contínua, integre semanage boolean -l -C (apenas modificados) ao seu pipeline de configuração, uma abordagem que combina bem com a auditoria de conformidade com Lynis e CIS Benchmarks que documentei anteriormente.

Como criar uma política SELinux personalizada com audit2allow

Quando nem boolean nem rótulo resolvem (por exemplo, um daemon proprietário que precisa escrever em um pipe nomeado em /run/myapp), você gera um módulo customizado a partir dos AVCs reais. Esse é o caminho profissional. Copiar e colar regras de Stack Overflow é o caminho que vira incidente.

Passo 1, colocar em permissive apenas o domínio do serviço:

# semanage permissive -a myapp_t
# semanage permissive -l | grep myapp_t
myapp_t

Isso é melhor que setenforce 0 global: o resto do sistema continua protegido enquanto você coleta dados.

Passo 2, exercitar a aplicação e coletar AVCs:

# ausearch -m AVC -ts recent
time->Mon Jun 22 14:30:11 2026
type=AVC msg=audit(1719066611.842:413): avc:  denied  { write } for
  pid=4521 comm="myappd" name="myapp.sock" dev="tmpfs" ino=98711
  scontext=system_u:system_r:myapp_t:s0
  tcontext=system_u:object_r:var_run_t:s0
  tclass=sock_file permissive=1

Passo 3, gerar o módulo:

# ausearch -m AVC -ts recent | audit2allow -M myapp-local
# cat myapp-local.te
module myapp-local 1.0;

require {
        type myapp_t;
        type var_run_t;
        class sock_file write;
}

#============= myapp_t ==============
allow myapp_t var_run_t:sock_file write;

Passo 4, instalar e reverter para enforcing:

# semodule -i myapp-local.pp
# semanage permissive -d myapp_t
# semodule -l | grep myapp-local
myapp-local

A documentação upstream do SELinux Project tem exemplos completos de .te, .fc e .if para quem quiser escrever política do zero em vez de partir de AVCs.

SELinux em contêineres: Podman, container_t e workloads confinados

A intersecção SELinux + contêineres é onde a maior parte das equipes tropeça em 2026. Quando você roda podman run -v /data:/data nginx com SELinux ativo, o processo dentro do contêiner roda como container_t com uma categoria MCS única (ex.: s0:c123,c456), e o diretório /data no host tem outro contexto. Resultado: permission denied mesmo com chmod 777.

O Podman tem duas opções para relabel automático de volumes:

  • :z rotula o volume como container_file_t compartilhado entre contêineres.
  • :Z rotula com categoria MCS privada, exclusiva daquele contêiner.
# Compartilhado entre vários contêineres
# podman run -d -v /data:/data:z docker.io/library/nginx

# Privado, isolado por MCS
# podman run -d -v /data/db:/var/lib/postgresql/data:Z docker.io/library/postgres:17

Em ambientes Kubernetes (k3s, OpenShift), o seLinuxOptions no securityContext do Pod controla o nível MCS por workload. Combinado com Pod Security Admission, isso bloqueia contêineres tentando rodar como spc_t (super privileged container). Para defesa em profundidade no runtime, o SELinux complementa muito bem ferramentas eBPF. Abordo essa camada com mais detalhe no comparativo entre Falco e Tetragon para segurança de runtime no Kubernetes.

O NIST SP 800-190 classifica isolamento MAC (categoria 3.1.3) como controle obrigatório para qualquer ambiente que rode múltiplos tenants no mesmo host. Sem SELinux ou equivalente, você está implicitamente confiando no kernel namespace de contêiner como única barreira, e CVEs como o CVE-2022-0185 já mostraram que isso não basta.

SELinux vs AppArmor: quando usar cada um

Ambos implementam MAC, ambos confinam processos, mas os modelos divergem em filosofia. Cobri o ecossistema AppArmor em detalhe no guia de hardening com AppArmor para Ubuntu e Debian. Aqui vai o resumo prático para a escolha.

DimensãoSELinuxAppArmor
ModeloType Enforcement + RBAC + MCS/MLSPath-based profiles
Distribuições padrãoRHEL, Rocky, AlmaLinux, Fedora, CentOS StreamUbuntu, Debian, openSUSE
Curva de aprendizadoÍngreme (tipos, atributos, contextos)Suave (perfis legíveis por humano)
GranularidadePor inode (sobrevive a rename, mount, bind)Por caminho (vulnerável a path manipulation)
Suporte a MLS/MCSSim, nativoNão
Comunidade de policyVasta (refpolicy)Menor, mas crescente
Melhor cenárioMulti-tenant, contêineres, compliance regulatóriaServidores single-tenant, edge, IoT

Minha regra de bolso: se o sistema roda workloads de múltiplos times no mesmo kernel, ou se compliance (PCI-DSS, FedRAMP, ISO 27001) menciona MAC nominalmente, use SELinux. Se é um appliance single-purpose onde a maior preocupação é confinar aquele daemon específico, o AppArmor é mais barato de operar e suficiente. Rodar os dois simultaneamente no mesmo kernel não é suportado: o LSM stack permite múltiplos módulos, mas as distribuições só ativam um por vez.

Solução de problemas: sealert, ausearch e logs AVC

Toda decisão de SELinux que importa termina em /var/log/audit/audit.log. Quando o auditd está rodando (padrão no RHEL 10), cada negação vira um evento AVC com timestamp, syscall, contexto de origem e destino. As três ferramentas que você vai usar todo dia:

# Eventos AVC dos últimos 10 minutos
# ausearch -m AVC,USER_AVC -ts recent

# Tradução humanizada dos AVCs (pacote setroubleshoot-server)
# sealert -a /var/log/audit/audit.log

# Resumo agregado por syscall/comando
# aureport -a --summary

Um exemplo real de saída do sealert:

SELinux is preventing /usr/sbin/nginx from getattr access on the file
/srv/sites/app/index.html.

*****  Plugin catchall_labels (83.8 confidence) suggests   ********

If you want to allow nginx to have getattr access on the index.html file
Then you need to change the label on /srv/sites/app/index.html
Do
# semanage fcontext -a -t httpd_sys_content_t '/srv/sites/app/index.html'
# restorecon -v '/srv/sites/app/index.html'

O sealert normalmente acerta na primeira sugestão para 70-80% dos AVCs. Para os outros, leia o evento bruto e pergunte: qual processo, em qual domínio, tentou qual operação em qual contexto? Essa é a única pergunta. Se a resposta envolve um processo confinado tentando ler segredos ou abrir sockets de rede inesperados, você acabou de detectar uma intrusão, não uma falha de configuração. Esse é o ponto onde o SELinux deixa de ser "incômodo" e vira sensor de segurança, e onde a integração com pipelines de detecção de intrusões com Wazuh, Auditd e Osquery paga todos os dividendos. Combine isso com hardening de SSH e você fechou a maior parte das portas de entrada que um operador remoto consegue empurrar.

Perguntas frequentes

Como desabilitar o SELinux temporariamente sem reboot?

Execute setenforce 0 para mudar para permissive. O SELinux continua carregado, registra AVCs, mas não bloqueia nada. Para reativar enforcement: setenforce 1. Essa abordagem é preferível a desabilitar completamente porque não exige reboot nem relabel posterior.

O SELinux atrapalha o desempenho do servidor?

Em workloads típicos de servidor, o overhead do SELinux fica abaixo de 7% em syscalls intensivas e é praticamente invisível em throughput de rede ou disco. O AVC cache do kernel 6.12 LTS reduz ainda mais esse custo. Desabilitar SELinux por motivo de performance raramente se justifica fora de cenários HFT.

O que fazer quando uma aplicação não funciona com SELinux?

Não desabilite. Coloque o domínio específico em permissive com semanage permissive -a <domain_t>, exercite a aplicação, colete AVCs com ausearch -m AVC -ts recent e gere um módulo de política com audit2allow -M nome-modulo. Revise cada regra antes de instalar com semodule -i e remova o permissive.

Posso usar SELinux junto com AppArmor no mesmo servidor?

Tecnicamente o LSM stack do kernel Linux suporta múltiplos módulos MAC carregados simultaneamente desde 5.1, mas nenhuma distribuição mainstream entrega ambos ativos. Escolha um por host conforme a distribuição e o caso de uso. Operar os dois em paralelo significaria manter duas políticas em sincronia, e o custo operacional não compensa.

Como saber qual boolean SELinux preciso ativar para meu serviço?

Liste todos os booleans com descrição usando semanage boolean -l e filtre pelo nome do serviço (ex.: | grep httpd). A coluna final é a descrição do que cada boolean libera. Se nenhum cobre seu cenário, é sinal de que você precisa de um módulo customizado, não de um boolean.

Aisha Okonkwo
Sobre o Autor Aisha Okonkwo

Infrastructure security architect at a hyperscaler. Spends her days on Zero Trust, secrets management, and yelling at unencrypted backups.