<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 && \
adduser -D -u 1000 -G appuser appuser
USER appuser
WORKDIR /app
COPY . .
CMD ["sh", "-c", "id"]</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">{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
]
}
],
"syscalls": [
{
"names": [
"accept4",
"arch_specific_syscall",
"bind",
"brk",
"clone",
"close",
"connect",
"dup",
"dup2",
"dup3",
"epoll_create1",
"epoll_ctl",
"epoll_wait",
"exit",
"exit_group",
"fcntl",
"fstat",
"fstatfs",
"futex",
"getcwd",
"getpeername",
"getpid",
"getrandom",
"getrlimit",
"getsockname",
"getsockopt",
"gettimeofday",
"listen",
"lseek",
"madvise",
"mmap",
"mprotect",
"mremap",
"munmap",
"nanosleep",
"open",
"openat",
"pipe",
"pipe2",
"poll",
"pread64",
"prlimit64",
"proctitle",
"pselect6",
"read",
"readv",
"recvfrom",
"recvmsg",
"restart_syscall",
"rt_sigaction",
"rt_sigprocmask",
"sched_getaffinity",
"sched_yield",
"seccomp",
"select",
"sendmsg",
"sendto",
"set_robust_list",
"set_tid_address",
"setitimer",
"setsockopt",
"sigaction",
"sigaltstack",
"sigpending",
"sigprocmask",
"sigsuspend",
"socket",
"socketpair",
"stat",
"statfs",
"statx",
"tgkill",
"time",
"timer_create",
"timer_delete",
"timer_gettime",
"timer_settime",
"timerfd_create",
"timerfd_gettime",
"timerfd_settime",
"tkill",
"uname",
"write",
"writev"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [
"ptrace"
],
"action": "SCMP_ACT_ALLOW",
"includes": {
"minKernel": "4.8"
}
}
]
}</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 "console.log('Hello')"
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 <tunables/global>
profile app-profile flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>
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 && \
adduser -D -u 1000 -G appuser appuser
USER appuser
WORKDIR /app
CMD ["./app.sh"]</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: '3.8'
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><!-- FIM --></p>