Sandboxes
O kubernetes somente é seguro se o container runtime for seguro.
Um container roda como um processo no kernel linux como qualquer outro processo do próprio host. A diferença é que este processo está limitado em um kernel group através dos namespaces como vimos anteriormente em review container. Se um container conseguir sair do seu kernel group e gerenciar diretamente o kernel ele poderá ter acesso a todos os outros processos que rodam nos host. Diferentemente, em uma VM nós temos um isolamento completo entre kernels.
O que é sandbox? Já ouvimos esse termo anteriormente em diferentes contextos, mas no caso de containers estamos falando em rodar um container com uma camada extra segurança para reduzir ataques
.
- Kernel Space
- Temos o hardware na camada inferior.
- Em uma camada acima controlando o hardware temos o kernel.
- O kernel provê uma camada de API ou interfaces (syscall ou system call) para que os processos se comuniquem com ele. Alguns exemplos:
ps
para ver os processos.ip link
para listar as interfaces de rede.reboot
Podemos fazer isso través de linha de comando ou usando bibliotecas em uma linguagem qualquer. Veremos melhor sobre isso mais pra frente no curso.
- User Space
- Qualquer tipo de processo que possa utilizar syscalls.
- O que não roda no kernel space roda no user space.
O que vamos fazer é incluir uma nova camada de segurança para controlar as chamadas de sistema. O container (processo) fará uma chamada para o sandbox e este fará a chamada de syscall atuando como um proxy no qual podemos criar restrições.
Normal | Sandbox |
---|---|
![]() | ![]() |
Obviamente teremos um custo de desempenho e recursos pois estamos colocando uma camada lógica a mais no caminho.
Sandboxes não é útil para qualquer tipo de aplicação.
- Melhor utilizado em containers menores. Em containers grandes pode causar sobrecarga.
- Não é bom para workloads com muitas chamadas de syscall geraria muito atraso.
- Não tem acesso direto ao hardware.
É útil para a maioria das aplicações, não todas.
Vamos mostrar uma chamada de syscall simples que é o uname
.
root@cks-master:~# k get pods
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 11h
root@cks-master:~# k exec nginx -it -- bash
# Dentro do container retornou o kernel usado
root@nginx:/# uname -r
5.15.0-1067-gcp
root@nginx:/# exit
exit
# Dentro do host retornou o kernel
root@cks-master:~# uname -r
5.15.0-1067-gcp
# Usando o strace podemos ver quais syscalls foram usadas.
# Vou reduzir a saída para ficar mais fácil a leitura
root@cks-master:~# strace uname -r
execve("/usr/bin/uname", ["uname", "-r"], 0x7ffd0d749d98 /* 29 vars */) = 0
brk(NULL) = 0x5636fe7ac000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffc99764540) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=23857, ...}) = 0
mmap(NULL, 23857, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5d304d2000
close(3) = 0
...
close(3) = 0
openat(AT_FDCWD, "/usr/lib/locale/C.UTF-8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=201272, ...}) = 0
mmap(NULL, 201272, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f5d2fe49000
close(3) = 0
uname({sysname="Linux", nodename="cks-master", ...}) = 0 # <<<<
fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(0x88, 0), ...}) = 0
write(1, "5.15.0-1067-gcp\n", 165.15.0-1067-gcp
) = 16
close(1) = 0
close(2) = 0
exit_group(0) = ?
+++ exited with 0 +++
Várias chamadas de sistema foram usados inclusive close, write, etc.
Dirt Cow
era vulnerabilidade crítica do Linux para escalonamento de privilégios no kernel, um bug relacionado ao processo copy-on-write (COW). Essa vulnerabilidade permitia que usuários locais sem privilégios conseguisssem obter acesso de escrita em partes da memória que deveria ser somente leitura e era explorado para obter acesso root. Tudo isso feito usando chamada de syscall.
Open Container Iniciative (OCI)
OCI é um projecto da Linux Foundation para projetar padrões para virtualização de containers.
-
Especificação
: Cria e mantém uma especificação para containers runtimes, definindo o que é um runtime, uma imagem, uma distribuição e por causa disso temos difefentes containers runtimes que seguem o mesmo padrão e podem rodar as mesmas imagens de container. O kubelet diferentes containers runtime desde que sigam as especificações da OCI. -
Runtime
: runc é o container runtime mantido pela OCI que implementa as especificações definidas.
O Docker não cria containers diretamente. Ele se comunica em diferentes camadas, sendo uma delas o containerd que se comunica com o runc. O runc implementa as especificações do OCI e cria os containers se comunicando com a libcontainer que interage com recursos do kernel do Linux como namespaces cgroups, chroot, para gerenciar containers sem depender de processos externo como o LXC.
O CRI, Container Runtime Interface permite a comunicação do kubelet com diferentes runtimes. A configuração do container runtime default pode ser definidas no arquivo de configuração do kubelet em /var/lib/kubelet/config.yaml
.
cat /var/lib/kubelet/config.yaml | grep container
containerRuntimeEndpoint: ""
root@cks-master:~# ps -aux | grep kubelet
root 939 2.4 1.6 2119304 66152 ? Ssl Aug27 18:00 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime-endpoint=unix:///var/run/containerd/containerd.sock --pod-infra-container-image=registry.k8s.io/pause:3.9 --container-runtime-endpoint unix:///run/containerd/containerd.sock
...
root@cks-master:~# cat /usr/lib/systemd/system/kubelet.service
[Unit]
Description=kubelet: The Kubernetes Node Agent
Documentation=https://kubernetes.io/docs/
Wants=network-online.target
After=network-online.target
[Service]
ExecStart=/usr/bin/kubelet
Restart=always
StartLimitInterval=0
RestartSec=10
[Install]
WantedBy=multi-user.target
root@cks-master:~# cat /etc/default/kubelet
KUBELET_EXTRA_ARGS="--container-runtime-endpoint unix:///run/containerd/containerd.sock"
Comparação Sandboxes
Temos aqui uma imagem que mostra alguns containers runtime que podemos utilizar.
Como falamos anteriormente, container sandboxes oferecem uma camada adicional de isolamento e segurança para containers, sendo especialmente úteis em ambientes multi-tenant ou quando há necessidade de um isolamento mais forte do que o que é oferecido por contêineres tradicionais. Algumas opções:
Kata Containers
Desenvolvido pela OpenStack Foundation, Kata Containers combina a velocidade dos contêineres com o isolamento de máquinas virtuais. Ele roda cada contêiner dentro de uma VM leve, usando tecnologias de virtualização para isolar o contêiner do sistema host, sem sacrificar a performance.
- Camada forte de separação.
- Fácil de gerenciar essas vms pois são criadas automaticamente.
- Cara container roda em uma virtual machine privada, mas é necessário que o host tenha um hypervisor.
- QEMU é o default e precisa de uma virtualização, a mesma utilizada em cloud.
- O problema que em cloud, virtualizar em cima de outra virtualização é um pouco mais complicado precisando ativar esse recurso e não sendo tão fácil de configurar.
Perfeito para workloads que exigem um isolamento mais forte, como serviços críticos em nuvem e ambiente multi tenant. Não é muito recomendado para kubernetes.
gVisor
Desenvolvido pela google, o gVisor é um runtime de contêineres que fornece uma camada extra de isolamento entre o contêiner e o kernel do sistema host, implementando um kernel user-space que intercepta e manipula chamadas do sistema.
- Outra camada de separação.
- Não necessita de um hypervisor.
- Simula as syscalls do kernel com limitação de funcionalidades. Na verdade o gVisor implementa seu próprio Kernel em golang que aceita as chamadas e manipula (transforma e ignora) fazendo a chamada real para o kernel no host.
- Roda no user space separado do kernel linux.
- Runtime é chamado de runsc.
Perfeito para workloads em que a compatibilidade com Kubernetes e Docker é importante, pois não sacrifica muito o desempenho. É leve, fácil de integrar em infraestruturas existentes e oferece uma camada de isolamento extra sem precisar rodar VMs completas como o Kata.
Nabla Containers
Nabla Containers, desenvolvido pela IBM, usa uma abordagem unikernel-like, onde o contêiner é executado com um conjunto extremamente reduzido de chamadas de sistema. Isso minimiza a superfície de ataque, pois o contêiner tem acesso a muito menos funcionalidades do kernel. Com menos chamadas de sistema expostas, a chance de exploração de vulnerabilidades é reduzida. Isso faz do Nabla uma opção muito segura, especialmente em cenários onde a superfície de ataque mínima é essencial.
A abordagem extremamente restritiva pode ser limitante para muitos workloads. Aplicações que precisam de mais interações com o sistema operacional podem não funcionar bem no Nabla. Sua utilização é mais especializada e pode requerer modificações nas imagens e nas bibiotecas utilizadas pela aplicação.
Indicado para ambientes onde o mínimo de superfície de ataque é desejado. Um host no kubernetes que utiliza o Nabla ficaria muito reduzido em compatiblidade, precisaria ser algo muito específico.
SCONE
Curiosidade somente, caso um dia precise, mas é uma tecnologia paga.
SCONE (Secure Computing Node Environment) oferece contêineres seguros que utilizam enclaves Intel SGX para garantir que os dados sejam protegidos mesmo em memória. É uma solução robusta para proteger dados sensíveis e aplicações.
Os dados são criptografados dentro dos enclaves (áreas de memória protegida que são isoladas do restante do sistema, mesmo que o kernel ou o hypervisor sejam comprometidos), garantindo que até mesmo administradores do sistema ou operadores do Kubernetes não possam acessar os dados sem autorização.
- A imagem precisa ser sconetizada logo depois do build.
- O node precisa possuir suporte ao SGX gerando custo extra.
- Conhecimento específicos para a configuração e desenvolvimento das aplicações.
Uso específico para plicações que manipulam dados altamente sensíveis ou que requerem segurança confidencial pois tudo é encriptado sendo difícil até para os próprios administradores. Não é uma solução free e seu uso é extremamente restrito.
Analizando o que temos acima, o que oferece a melhor performance/compatiblidade sem necessidade de muita intervenção é o gVisor. Claro que estamos falando de uma situação genérica.