Skip to main content

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.

alt text

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.

NormalSandbox
alt textalt text

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.

alt text

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.

alt text

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

alt text

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.

alt text

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

alt text

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.

alt text

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

alt text

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

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.