Security Context
Container vs Host vs Privilégios
Primeiro vamos entender um pouco de segurança em containers, pois só faz sentindo no Kubernetes se entendermos containers.
O host que hospeda os containers tem seus próprios processos em execução que carregam o sistema operacional, o daemon do Docker se tiver o Docker ou algum outro container runtime.
Para fazer uma análise vamos rodar um container qualquer e coloca-lo para dormir por 1 hora para que tenha algo segurando o processo e ele não morra.
docker run --name ubuntu ubuntu sleep 3600
O container compartilha o mesmo kernel do host, mas são isolados através de namespaces no Linux. O host tem o seu namespace e cada container tem o seu separado.
Por isso o container somente pode ver os seus processos e nada fora dele.
Dentro do container:
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
764da973f0fe ubuntu "sleep 3600" About a minute ago Up About a minute ubuntu
docker exec -it ubuntu ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 2792 1408 ? Ss 08:04 0:00 sleep 3600
root 7 0.0 0.0 7064 2944 pts/0 Rs+ 08:05 0:00 ps aux
O processo que segura o container funcionando tem o pid 1.
No host.
git:(main) ✗ ps -aux | grep sleep
david-p+ 1628755 0.0 0.0 2066812 25536 pts/2 Sl+ 05:04 0:00 docker run --name ubuntu ubuntu sleep 3600
root 1628843 0.0 0.0 2792 1408 ? Ss 05:04 0:00 sleep 3600
david-p+ 1631734 0.0 0.0 9220 2560 pts/5 R+ 05:08 0:00 grep --color=auto sleep
No host podemos ver o processo do container que é o sleep 3600 mas com um pid diferente. Isso porque os processos podem ter diferentes PIDs em diferentes namespaces, é assim que o Docker funciona.
Se criarmos outro container o que podemos ver?
docker run --name centos centos sleep 3600
docker exec -it centos ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 23048 2560 ? Ss 08:14 0:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 3600
root 7 1.0 0.0 44668 3456 pts/0 Rs+ 08:16 0:00 ps aux
# e no host
ps -aux | grep sleep
david-p+ 1628755 0.0 0.0 2066812 24948 pts/2 Sl+ 05:04 0:00 docker run --name ubuntu ubuntu sleep 3600
root 1628843 0.0 0.0 2792 1408 ? Ss 05:04 0:00 sleep 3600
david-p+ 1634805 0.0 0.0 1992824 26052 pts/5 Sl+ 05:14 0:00 docker run --name centos centos sleep 3600
root 1634985 0.0 0.0 23048 2560 ? Ss 05:14 0:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 3600
david-p+ 1639928 0.0 0.0 9220 2560 pts/4 S+ 05:17 0:00 grep --color=auto sleep
Podemos analisar aqui que dentro do container estamos rodando o processo como root user.
Podemos forçar que seja outro user rode o processo passando no comando --user 1000. Geralmente o 1000 é o primeiro usuário criado fora o root. Na minha máquina pessoal 1001 é o id do meu usuário.
docker run --name ubuntu --user 1001 ubuntu sleep 3600
docker exec -it ubuntu ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
1001 1 0.0 0.0 2792 1536 ? Ss 08:23 0:00 sleep 3600
1001 7 0.0 0.0 7064 2944 pts/0 Rs+ 08:23 0:00 ps aux
Ou definir no Dockerfile da imagem que vamos buildar criando uma imagem personalizada.
FROM ubuntu
USER 1001
Agora no host temos.
ps aux | grep sleep
david-p+ 1658884 0.0 0.0 1845040 25728 pts/9 Sl+ 05:38 0:00 docker run --name ubuntu --user 1001 ubuntu sleep 3600
david-p+ 1658972 0.0 0.0 2792 1408 ? Ss 05:38 0:00 sleep 3600
david-p+ 1660399 0.0 0.0 9220 2688 pts/8 S+ 05:40 0:00 grep --color=auto sleep
E quem está executando o processo? david-p....
O que podemos perceber é que o usamos o mesmo usuário dentro do container e do host. O mesmo root do container é o root do sistema e o mesmo usuário 1001 do container é o 1001 do host que é o meu próprio e apareceu ali como o sleep sendo um processo do meu usuário.
Mas não era para ser isolado? Se isso for verdade não é perigoso?
O processo dentro do container pode fazer qualquer coisa que o usuário root possa fazer no host? Na verdade não. O Docker utiliza um recurso do Linux chamado capabilities que limita os poderes do root dentro do container ou do usuário passado, mas é o mesmo usuário com poderes limitados.
O usuário root pode fazer qualquer coisa no sistema host como já sabemos.
Para ver o que um usuário pode fazer podemos analisar o arquivo /usr/include/linux/capability.h
Se quisermos retirar essa limitação para que o usuário root do container possa fazer mais coisas podemos fazer.
# Esse container já é executado como root e --privileged habilitada todos os privilégios do root do container dentro do host.
docker run --name ubuntu --privileged ubuntu sleep 3600
#ou somente uma permissão específica --cap-drop retiraria uma permissão
docker run --name ubuntu --cap-add CHOWN ubuntu sleep 3600
Aqui lista.
Chave de capacidade | Descrição da capacidade |
---|---|
AUDIT_WRITE | Grave registros no log de auditoria do kernel. |
CHOWN | Faça alterações arbitrárias nos UIDs e GIDs dos arquivos (veja chown(2)). |
DAC_OVERRIDE | Ignore verificações de permissão de leitura, gravação e execução de arquivos. |
FOWNER | Ignore verificações de permissão em operações que normalmente exigem que o UID do sistema de arquivos do processo corresponda ao UID do arquivo. |
FSETID | Não limpe os bits de permissão set-user-ID e set-group-ID quando um arquivo for modificado. |
KILL | Ignore verificações de permissão para envio de sinais. |
MKNOD | Crie arquivos especiais usando mknod(2). |
NET_BIND_SERVICE | Vincule um soquete a portas privilegiadas de domínio da Internet (números de porta menores que 1024). |
NET_RAW | Use soquetes RAW e PACKET. |
SETFCAP | Defina recursos de arquivo. |
SETGID | Faça manipulações arbitrárias de GIDs de processo e lista de GID suplementar. |
SETPCAP | Modifique os recursos do processo. |
SETUID | Faça manipulações arbitrárias de UIDs de processos. |
SYS_CHROOT | Use chroot(2), altere o diretório raiz. |
AUDIT_CONTROL | Habilite e desabilite a auditoria do kernel; alterar regras de filtro de auditoria; recuperar status de auditoria e regras de filtragem. |
AUDIT_READ | Permitir a leitura do log de auditoria via soquete netlink multicast. |
BLOCK_SUSPEND | Permitir evitar suspensões do sistema. |
BPF | Permitir a criação de mapas BPF, carregamento de dados BPF Type Format (BTF), recuperação de código JITed de programas BPF e muito mais. |
CHECKPOINT_RESTORE | Permitir operações relacionadas a pontos de verificação/restauração. Introduzido no kernel 5.9. |
DAC_READ_SEARCH | Ignore verificações de permissão de leitura de arquivo e verificações de permissão de leitura e execução de diretório. |
IPC_LOCK | Bloquear memória (mlock(2), mlockall(2), mmap(2), shmctl(2)). |
IPC_OWNER | Ignore verificações de permissão para operações em objetos IPC do System V. |
ALUGAR | Estabeleça concessões em arquivos arbitrários (veja fcntl(2)). |
LINUX_IMMUTABLE | Defina os sinalizadores de i-node FS_APPEND_FL e FS_IMMUTABLE_FL. |
MAC_ADMIN | Permitir configuração MAC ou alterações de estado. Implementado para o Smack LSM. |
MAC_OVERRIDE | Substitua o controle de acesso obrigatório (MAC). Implementado para o Módulo de Segurança Smack Linux (LSM). |
NET_ADMIN | Execute várias operações relacionadas à rede. |
NET_BROADCAST | Faça transmissões de soquete e ouça multicasts. |
PERFMON | Permitir operações privilegiadas de desempenho e observabilidade do sistema usando perf_events, i915_perf e outros subsistemas do kernel |
SYS_ADMIN | Execute uma série de operações de administração do sistema. |
SYS_BOOT | Use reboot(2) e kexec_load(2), reinicialize e carregue um novo kernel para execução posterior. |
SYS_MODULE | Carregar e descarregar módulos do kernel. |
SYS_NICE | Aumente o valor legal do processo (nice(2), setpriority(2)) e altere o valor legal para processos arbitrários. |
SYS_PACCT | Use acct(2), ative ou desative a contabilidade do processo. |
SYS_PTRACE | Rastreie processos arbitrários usando ptrace(2). |
SYS_RAWIO | Execute operações de porta de E/S (iopl(2) e ioperm(2)). |
SYS_RESOURCE | Substituir limites de recursos. |
SYS_TIME | Definir relógio do sistema (settimeofday(2), stime(2), adjtimex(2)); definir o relógio em tempo real (hardware). |
SYS_TTY_CONFIG | Utilize vhangup(2); empregar várias operações ioctl(2) privilegiadas em terminais virtuais. |
SYSLOG | Execute operações syslog(2) privilegiadas. |
WAKE_ALARM | Acione algo que ativará o sistema. |
Um user com essas permissões poderia simplesmente matar processos do sistema, rebootar a máquina, mudar configurações redes, criar novos users, mudar permissões de arquivos, etc.
docker run -it --name ubuntu --privileged ubuntu bash
root@a40542f4c76e:~# reboot
bash: reboot: command not found
A imagem do ubuntu é um imagem minima sem muitos utilitário o que já ajuda na segurança. O alpine é ainda pior, com muito menos. É por isso que o alpine é top. O menos é mais!
docker run -it --name centos --privileged centos:8 bash
reboot
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
Failed to talk to init daemon.
# Dentro do contaienr
[root@5622c6f14e5e /]# reboot -f
Rebooting.
#
docker ps -a | grep centos
5622c6f14e5e 5d0da3dc9764 "bash" 2 minutes ago Exited (129) 2 minutes ago centos
Nesse caso o container rebootou não o host, mas ele rebootou, ou seja, teve privilégios para executar o comando.
O uso da flag --privileged ao iniciar um contêiner Docker concede privilégios adicionais ao contêiner, permitindo que ele tenha acesso a dispositivos e recursos do sistema host. No entanto, essa opção não concede acesso direto ao sistema host fora do contêiner.
Quando você executa comandos dentro de um contêiner Docker, eles são isolados do sistema hospedeiro, mesmo quando o contêiner é iniciado com a opção --privileged. Isso é uma característica fundamental da tecnologia de contêineres, que visa garantir a segurança e o isolamento entre os processos do contêiner e os processos do host.
Portanto, mesmo que você tenha privilégios adicionais dentro do contêiner usando --privileged, o acesso ao host ainda é restrito. Isso significa que, mesmo como usuário root dentro do contêiner, você não terá acesso direto ao sistema host, a menos que utilize técnicas específicas, como montar o sistema de arquivos do host dentro do contêiner (o que pode ser arriscado e não é recomendado, a menos que necessário).
Entendendo isso agora tudo o que podemos fazer no Kubernetes.
O securityContext pode ser aplicado em todos os containers do pod se declarado em nível de spec, somente a um container específico se declarado a nível do container ou ambos sendo que no nível o nível de container tem preferência ao nível de pod. Porém, as capabilities somente são aplicadas a nível de container
.
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: ubuntu
name: ubuntu
spec:
#Nível de pod
securityContext:
runAsUser: 1000
containers:
- name: ubuntu
command: ["sleep","3600"]
image: ubuntu
# securityContext:
# runAsUser: 1001 << mudando o usuário
# capabilities:
# add: ["NET_ADMIN","SYSLOG"]
# Mantendo o mesmo usuário mas adicionando capabilities
# securityContext:
# capabilities:
# add: ["NET_ADMIN","SYSLOG"]
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
Para saber com qual usuário o container dentro do pod está rodando
#PODS #CONTAINER # COMMAND
kubectl exec pods/nginx-david-c8644f94d-nlgch nginx -- whoami