Docker & Kubernetes

Dominando Segurança em Docker: Rootless Containers, Seccomp e AppArmor em Projetos Reais

16 min de leitura

Dominando Segurança em Docker: Rootless Containers, Seccomp e AppArmor em Projetos Reais

Introdução: O Cenário Atual de Segurança em Containers Docker revolucionou a forma como desenvolvemos e deployamos aplicações, mas trouxe consigo responsabilidades significativas de segurança. Quando você executa um container, está essencialmente executando processos isolados no kernel do host, compartilhando recursos críticos. Por padrão, containers rodam como root, o que amplifica consideravelmente os riscos de segurança. Uma vulnerabilidade em uma aplicação containerizada pode comprometer não apenas o container, mas potencialmente o host inteiro. Neste artigo, vamos explorar três camadas essenciais de segurança em Docker que, quando implementadas corretamente, reduzem drasticamente a superfície de ataque. Você compreenderá não apenas como implementar essas tecnologias, mas por que cada uma delas é crítica para uma estratégia de segurança robusta em ambientes containerizados. Rootless Containers: Executando Docker Sem Privilégios de Root O Problema do Root em Containers Quando você executa , por padrão, você obtém um shell com privilégios UID 0 (root) dentro do container. Se um atacante conseguir escapar do isolamento do container, ele

<h2>Introdução: O Cenário Atual de Segurança em Containers</h2>

<p>Docker revolucionou a forma como desenvolvemos e deployamos aplicações, mas trouxe consigo responsabilidades significativas de segurança. Quando você executa um container, está essencialmente executando processos isolados no kernel do host, compartilhando recursos críticos. Por padrão, containers rodam como root, o que amplifica consideravelmente os riscos de segurança. Uma vulnerabilidade em uma aplicação containerizada pode comprometer não apenas o container, mas potencialmente o host inteiro.</p>

<p>Neste artigo, vamos explorar três camadas essenciais de segurança em Docker que, quando implementadas corretamente, reduzem drasticamente a superfície de ataque. Você compreenderá não apenas <em>como</em> implementar essas tecnologias, mas <em>por que</em> cada uma delas é crítica para uma estratégia de segurança robusta em ambientes containerizados.</p>

<h2>Rootless Containers: Executando Docker Sem Privilégios de Root</h2>

<h3>O Problema do Root em Containers</h3>

<p>Quando você executa <code>docker run -it ubuntu bash</code>, por padrão, você obtém um shell com privilégios UID 0 (root) dentro do container. Se um atacante conseguir escapar do isolamento do container, ele terá acesso ao sistema com privilégios elevados. O Rootless Docker é um modo de execução onde o daemon Docker e os containers rodam como um usuário não-root no host, eliminando completamente esse vetor de ataque.</p>

<p>A diferença é fundamental: em um container rootless, mesmo que um atacante escape do isolamento, ele será um usuário comum, não root. Isso é segurança em profundidade — você não confia que o isolamento do container é perfeito, então adiciona uma camada extra de proteção.</p>

<h3>Instalação e Configuração do Rootless Docker</h3>

<p>Primeiro, você precisa remover a instalação padrão do Docker e instalar o rootless. Aqui está o processo em um sistema Ubuntu:</p>

<pre><code class="language-bash"># 1. Remover Docker padrão (se instalado)

sudo apt-get remove docker docker-engine docker.io containerd runc

2. Instalar dependências necessárias

sudo apt-get install -y uidmap dbus-user-session

3. Download e instalação do Docker Rootless

curl -fsSL https://get.docker.com/rootless | sh

4. Configurar o ambiente do usuário

export PATH=/home/$USER/bin:$PATH

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock

5. Ativar o systemd user service para iniciar automaticamente

systemctl --user enable docker

systemctl --user start docker

6. Verificar se está funcionando

docker ps</code></pre>

<h3>Verificando o Comportamento do Rootless</h3>

<p>Vamos criar um teste prático para demonstrar como o rootless muda o comportamento do UID:</p>

<pre><code class="language-bash"># Com Docker padrão (rootful), você veria UID 0:

docker run alpine id

uid=0(root) gid=0(root) groups=0(root)

Com Docker rootless, mesmo sem especificar usuário, você vê mapeamento:

docker run alpine id

uid=0(root) gid=0(root) groups=0(root) # Dentro do container (mapeado)

Mas no host:

ps aux | grep docker-containerd

Mostrará processos rodando como seu usuário regular, não root</code></pre>

<p>A mágica acontece através do <strong>user namespace mapping</strong>. O UID 0 dentro do container é mapeado para um UID diferente (geralmente na faixa 100000+) no host. Se você executar um comando malicioso que escape e tentar fazer algo como <code>rm -rf /</code>, ele será ejecutado como um usuário sem privilégios no host.</p>

<pre><code class="language-dockerfile"># Dockerfile exemplo com usuário explícito

FROM alpine:latest

RUN addgroup -g 1000 appuser &amp;&amp; \

adduser -D -u 1000 -G appuser appuser

USER appuser

WORKDIR /app

COPY . .

CMD [&quot;sh&quot;, &quot;-c&quot;, &quot;id&quot;]</code></pre>

<pre><code class="language-bash"># Ao executar:

docker build -t myapp .

docker run myapp

uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)</code></pre>

<h3>Limitações do Rootless</h3>

<p>O rootless Docker tem algumas limitações que você precisa estar ciente. Network privilegiado (portas abaixo de 1024) requer configuração extra, pois o usuário não pode vincular a portas privilegiadas. Drivers de volume e rede têm comportamento ligeiramente diferente. Além disso, nem todos os drivers de storage são suportados igualmente. Para a maioria das aplicações modernas, essas limitações são aceitáveis, mas em ambientes legados pode haver conflitos.</p>

<h2>Seccomp: Restringindo Chamadas de Sistema</h2>

<h3>O Que É Seccomp e Por Que Importa</h3>

<p>Seccomp (Secure Computing) é um mecanismo do kernel Linux que limita as syscalls (chamadas de sistema) que um processo pode fazer. Imagine que sua aplicação Node.js precisa apenas de syscalls de leitura, escrita e rede. Por que permitir que ela execute syscalls para criar dispositivos, carregar módulos de kernel ou trocar de usuário? Seccomp funciona como um firewall para syscalls.</p>

<p>Docker vem com um perfil Seccomp padrão que já bloqueia centenas de syscalls perigosas. Mas você pode e deve criar perfis customizados para suas aplicações específicas. Quanto mais restritivo, menor a superfície de ataque.</p>

<h3>Perfil Seccomp Padrão do Docker</h3>

<pre><code class="language-bash"># Visualizar o perfil padrão que Docker usa

docker run --rm alpine cat /proc/self/status | grep Seccomp

Executar sem Seccomp (MUITO inseguro, apenas para testes)

docker run --security-opt seccomp=unconfined alpine cat /proc/self/status</code></pre>

<h3>Criando um Perfil Seccomp Customizado</h3>

<p>Aqui está um perfil Seccomp customizado para uma aplicação web simples:</p>

<pre><code class="language-json">{

&quot;defaultAction&quot;: &quot;SCMP_ACT_ERRNO&quot;,

&quot;defaultErrnoRet&quot;: 1,

&quot;archMap&quot;: [

{

&quot;architecture&quot;: &quot;SCMP_ARCH_X86_64&quot;,

&quot;subArchitectures&quot;: [

&quot;SCMP_ARCH_X86&quot;,

&quot;SCMP_ARCH_X32&quot;

]

}

],

&quot;syscalls&quot;: [

{

&quot;names&quot;: [

&quot;accept4&quot;,

&quot;arch_specific_syscall&quot;,

&quot;bind&quot;,

&quot;brk&quot;,

&quot;clone&quot;,

&quot;close&quot;,

&quot;connect&quot;,

&quot;dup&quot;,

&quot;dup2&quot;,

&quot;dup3&quot;,

&quot;epoll_create1&quot;,

&quot;epoll_ctl&quot;,

&quot;epoll_wait&quot;,

&quot;exit&quot;,

&quot;exit_group&quot;,

&quot;fcntl&quot;,

&quot;fstat&quot;,

&quot;fstatfs&quot;,

&quot;futex&quot;,

&quot;getcwd&quot;,

&quot;getpeername&quot;,

&quot;getpid&quot;,

&quot;getrandom&quot;,

&quot;getrlimit&quot;,

&quot;getsockname&quot;,

&quot;getsockopt&quot;,

&quot;gettimeofday&quot;,

&quot;listen&quot;,

&quot;lseek&quot;,

&quot;madvise&quot;,

&quot;mmap&quot;,

&quot;mprotect&quot;,

&quot;mremap&quot;,

&quot;munmap&quot;,

&quot;nanosleep&quot;,

&quot;open&quot;,

&quot;openat&quot;,

&quot;pipe&quot;,

&quot;pipe2&quot;,

&quot;poll&quot;,

&quot;pread64&quot;,

&quot;prlimit64&quot;,

&quot;proctitle&quot;,

&quot;pselect6&quot;,

&quot;read&quot;,

&quot;readv&quot;,

&quot;recvfrom&quot;,

&quot;recvmsg&quot;,

&quot;restart_syscall&quot;,

&quot;rt_sigaction&quot;,

&quot;rt_sigprocmask&quot;,

&quot;sched_getaffinity&quot;,

&quot;sched_yield&quot;,

&quot;seccomp&quot;,

&quot;select&quot;,

&quot;sendmsg&quot;,

&quot;sendto&quot;,

&quot;set_robust_list&quot;,

&quot;set_tid_address&quot;,

&quot;setitimer&quot;,

&quot;setsockopt&quot;,

&quot;sigaction&quot;,

&quot;sigaltstack&quot;,

&quot;sigpending&quot;,

&quot;sigprocmask&quot;,

&quot;sigsuspend&quot;,

&quot;socket&quot;,

&quot;socketpair&quot;,

&quot;stat&quot;,

&quot;statfs&quot;,

&quot;statx&quot;,

&quot;tgkill&quot;,

&quot;time&quot;,

&quot;timer_create&quot;,

&quot;timer_delete&quot;,

&quot;timer_gettime&quot;,

&quot;timer_settime&quot;,

&quot;timerfd_create&quot;,

&quot;timerfd_gettime&quot;,

&quot;timerfd_settime&quot;,

&quot;tkill&quot;,

&quot;uname&quot;,

&quot;write&quot;,

&quot;writev&quot;

],

&quot;action&quot;: &quot;SCMP_ACT_ALLOW&quot;

},

{

&quot;names&quot;: [

&quot;ptrace&quot;

],

&quot;action&quot;: &quot;SCMP_ACT_ALLOW&quot;,

&quot;includes&quot;: {

&quot;minKernel&quot;: &quot;4.8&quot;

}

}

]

}</code></pre>

<p>Salve este arquivo como <code>seccomp-profile.json</code>. A estrutura é simples: <code>defaultAction</code> define o comportamento padrão (neste caso, bloquear com erro), e o array <code>syscalls</code> lista quais chamadas são permitidas.</p>

<h3>Aplicando Seccomp em um Container</h3>

<pre><code class="language-bash"># Executar com perfil customizado

docker run --security-opt seccomp=seccomp-profile.json alpine id

Executar com Seccomp desativado (inseguro!)

docker run --security-opt seccomp=unconfined alpine id

Verificar quais syscalls foram bloqueadas

docker run --security-opt seccomp=seccomp-profile.json alpine strace -e trace=open ls

Você verá ENOSYS (Function not implemented) para syscalls bloqueadas</code></pre>

<h3>Exemplo Prático: Ajustando Perfil para Node.js</h3>

<p>Se você tem uma aplicação Node.js simples que apenas faz requisições HTTP, pode ser ainda mais restritivo:</p>

<pre><code class="language-bash"># Inicie um container e capture as syscalls usadas

docker run --security-opt seccomp=unconfined node:alpine node -e &quot;console.log(&#039;Hello&#039;)&quot;

Analise com ferramentas como syscalltracer para criar um perfil mínimo

Remova todas as syscalls desnecessárias do seu perfil JSON</code></pre>

<h2>AppArmor: Controle Obrigatório de Acesso</h2>

<h3>O Que É AppArmor</h3>

<p>AppArmor é um sistema de controle obrigatório de acesso (Mandatory Access Control) que restringe o que um programa pode fazer baseado em um perfil. Enquanto Seccomp restringe syscalls, AppArmor restringe quais arquivos um processo pode acessar, quais redes pode usar, quais capacidades de kernel pode ter. É outra camada de defesa em profundidade.</p>

<p>Docker vem com um perfil AppArmor padrão chamado <code>docker-default</code>. Você pode criar perfis customizados para aplicações específicas que exigem proteção extra.</p>

<h3>Verificando AppArmor no Sistema</h3>

<pre><code class="language-bash"># Verificar se AppArmor está instalado e ativo

sudo systemctl status apparmor

Listar perfis AppArmor carregados

sudo aa-status

Procurar por perfis Docker

sudo aa-status | grep docker</code></pre>

<h3>Perfil AppArmor Padrão do Docker</h3>

<pre><code class="language-bash"># O perfil padrão está aqui

cat /etc/apparmor.d/docker-default</code></pre>

<p>O perfil padrão do Docker permite acesso a muitos caminhos. Para uma aplicação específica, você quer ser mais restritivo.</p>

<h3>Criando um Perfil AppArmor Customizado</h3>

<p>Vamos criar um perfil para uma aplicação que apenas lê dados de <code>/app</code> e escreve em <code>/tmp</code>:</p>

<pre><code class="language-apparmor">#include &lt;tunables/global&gt;

profile app-profile flags=(attach_disconnected,mediate_deleted) {

#include &lt;abstractions/base&gt;

#include &lt;abstractions/nameservice&gt;

#include &lt;abstractions/openssl&gt;

Permitir ler binários e bibliotecas do sistema

/lib/** mr,

/usr/lib/** mr,

/usr/bin/** mr,

/bin/** mr,

Permitir leitura de dados da aplicação

/app/** r,

Permitir escrita em diretório temporário

/tmp/** rw,

Permitir comunicação de rede

network inet stream,

network inet dgram,

Permitir sinais básicos

signal (send) type=term peer=unconfined,

signal (receive) type=term,

Bloquear acesso perigoso ao sistema de arquivos

deny /sys/** rwk,

deny /proc/** rwk,

deny /root/** rwk,

deny /home/** rwk,

Negar syscalls de kernel module

deny /sys/module/** rw,

}</code></pre>

<p>Salve como <code>/etc/apparmor.d/app-profile</code>.</p>

<h3>Carregando e Testando o Perfil</h3>

<pre><code class="language-bash"># Validar a sintaxe do perfil

sudo apparmor_parser -r /etc/apparmor.d/app-profile

Colocar em modo de log (aprendizado) primeiro

sudo aa-complain /etc/apparmor.d/app-profile

Ver denials no log

sudo tail -f /var/log/audit/audit.log | grep apparmor

Depois de ajustado, colocar em modo enforce

sudo aa-enforce /etc/apparmor.d/app-profile

Usar o perfil no Docker

docker run --security-opt apparmor=app-profile alpine ls /app</code></pre>

<h3>Integração de AppArmor com Dockerfile</h3>

<pre><code class="language-dockerfile">FROM alpine:latest

Criar estrutura de diretórios esperada pelo perfil

RUN mkdir -p /app /tmp

Copiar aplicação

COPY app.sh /app/

Dar permissões

RUN chmod +x /app/app.sh

Usuário não-root

RUN addgroup -g 1000 appuser &amp;&amp; \

adduser -D -u 1000 -G appuser appuser

USER appuser

WORKDIR /app

CMD [&quot;./app.sh&quot;]</code></pre>

<pre><code class="language-bash"># Build e execução com AppArmor

docker build -t secure-app .

docker run --security-opt apparmor=app-profile secure-app</code></pre>

<h2>Combinando as Três Camadas: Uma Estratégia Completa</h2>

<p>Segurança real vem de múltiplas camadas. Um perfil Seccomp bem configurado pode bloquear uma syscall perigosa. Um AppArmor bem configurado pode bloquear o acesso a arquivos críticos. E o Rootless Docker garante que, mesmo se ambos falharem, o atacante não terá privilégios elevados.</p>

<pre><code class="language-yaml"># docker-compose.yml com todas as camadas

version: &#039;3.8&#039;

services:

webapp:

image: myapp:latest

security_opt:

  • no-new-privileges:true
  • seccomp=seccomp-profile.json
  • apparmor=app-profile

cap_drop:

  • ALL

cap_add:

  • NET_BIND_SERVICE

read_only: true

tmpfs:

  • /tmp
  • /run

networks:

  • isolated

restart: on-failure

networks:

isolated:

driver: bridge</code></pre>

<p>Este docker-compose demonstra como combinar:</p>

<ul>

<li><strong>no-new-privileges</strong>: Previne que o processo ganhe privilégios</li>

<li><strong>Seccomp</strong>: Restringe syscalls</li>

<li><strong>AppArmor</strong>: Restringe acesso a arquivos e redes</li>

<li><strong>cap_drop/add</strong>: Controla capabilities do Linux</li>

<li><strong>read_only</strong>: Sistema de arquivos somente leitura</li>

<li><strong>tmpfs</strong>: Espaço temporário isolado</li>

</ul>

<h2>Conclusão</h2>

<p>Ao longo deste artigo, você aprendeu três tecnologias complementares que formam a base de uma estratégia robusta de segurança em Docker. <strong>Rootless Containers</strong> eliminam o risco de escalonamento de privilégios ao host, garantindo que mesmo em caso de escape do isolamento, o atacante tenha apenas permissões de usuário comum. <strong>Seccomp</strong> funciona como um firewall de baixo nível, bloqueando chamadas de sistema perigosas antes que possam ser executadas, reduzindo drasticamente a superfície de ataque do kernel. <strong>AppArmor</strong> implementa controle obrigatório de acesso, garantindo que processos só possam acessar os recursos especificamente necessários para sua operação, bloqueando tanto acesso ao sistema de arquivos quanto operações de rede não autorizadas. A verdadeira segurança em containers não vem de depender em uma única tecnologia, mas de aplicar essas camadas em conjunto — defesa em profundidade que aumenta exponencialmente a dificuldade de um ataque bem-sucedido.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.docker.com/engine/security/" target="_blank" rel="noopener noreferrer">Docker Security Official Documentation</a></li>

<li><a href="https://github.com/moby/moby/tree/master/profiles/seccomp" target="_blank" rel="noopener noreferrer">Seccomp Security Profiles</a></li>

<li><a href="https://ubuntu.com/tutorials/apparmor-introduction" target="_blank" rel="noopener noreferrer">AppArmor User Guide</a></li>

<li><a href="https://docs.docker.com/engine/security/rootless/" target="_blank" rel="noopener noreferrer">Rootless Docker Installation</a></li>

<li><a href="https://man7.org/linux/man-pages/man7/capabilities.7.html" target="_blank" rel="noopener noreferrer">Linux Capabilities and Container Security</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

Guia Completo de Services em Kubernetes: ClusterIP, NodePort, LoadBalancer e ExternalName
Guia Completo de Services em Kubernetes: ClusterIP, NodePort, LoadBalancer e ExternalName

O que é um Service no Kubernetes Um Service no Kubernetes é um objeto que exp...

Dominando Istio Avançado: Circuit Breaker, Fault Injection e Observabilidade em Projetos Reais
Dominando Istio Avançado: Circuit Breaker, Fault Injection e Observabilidade em Projetos Reais

Circuit Breaker em Istio: Proteção Contra Falhas em Cascata O Circuit Breaker...

Dominando Imagens Distroless e Alpine: Segurança e Tamanho em Produção em Projetos Reais
Dominando Imagens Distroless e Alpine: Segurança e Tamanho em Produção em Projetos Reais

O Que São Imagens Distroless? Imagens distroless são contêineres Docker minim...