Skip to main content

securityContext do Kubernetes: recursos do Linux no Kubernetes

Escrito por
Kubernetes securityContext

26 de janeiro de 2021

0 minutos de leitura

Nos primórdios dos sistemas operacionais Unix, o modelo de permissões era relativamente simples. Ou você era um usuário comum, ou era root, o superusuário com permissão para fazer tudo.

Embora fosse possível conceder permissões elevadas a usuários comuns em arquivos ou diretórios, quase todas as funções no nível do kernel eram restritas ao usuário root. Se o seu aplicativo precisasse de uma única chamada no nível do kernel para funcionar, teria que receber privilégios SUID — o que, na prática, concederia privilégios de root ao processo se ele fosse iniciado por um usuário comum.

Esse modelo funcionava bem quando máquinas físicas eram usadas por grupos relativamente pequenos de pessoas, mas essa granularidade grosseira já não atendia às necessidades do mundo moderno. Para oferecer mais flexibilidade e segurança, os desenvolvedores do kernel Linux criaram uma solução muito mais granular: agruparam chamadas no nível do kernel em recursos e permitiram atribuí-los a processos individuais. Assim, se um aplicativo precisasse de uma única chamada do kernel, bastaria atribuir a ele o recurso correspondente, limitando a exposição do sistema a riscos de segurança.

Gerenciamento de privilégios em contêineres do Kubernetes

Como isso funciona nos contêineres?

Na prática, um contêiner é apenas um processo em execução no sistema, isolado por meio de cgroups e namespaces do kernel. Isso significa que os recursos podem ser atribuídos a ele da mesma forma que a qualquer outro processo. O runtime de contêineres faz isso ao criar o contêiner.

Normalmente, o runtime de contêineres atribui um conjunto padrão de recursos ao contêiner (você pode ver aqui o conjunto padrão fornecido pelo Docker) e também oferece um mecanismo para adicionar ou remover recursos. Se você executar um contêiner com a flag --privileged, concederá a ele todos os recursos. Se o contêiner for executado como root, isso poderá causar um problema grave de segurança — um assunto que explorei em uma publicação anterior.

Configuração de recursos para um contêiner no securityContext do Kubernetes

Ao usar o runtime de contêineres no Kubernetes, o plano de controle do Kubernetes usa esses controles para definir quais recursos serão atribuídos ao contêiner quando ele for iniciado. As configurações de recursos são disponibilizadas ao usuário por meio de várias opções na seção securityContext do YAML do contêiner. A configuração fica assim:

securityContext:
      capabilities:
        drop:
          - ALL
        add: [“NET_ADMIN”]

Neste caso, removeríamos todos os recursos e adicionaríamos o recurso CAP_NET_ADMIN. Se a seção de recursos em securityContext estiver vazia, teremos o conjunto padrão definido pelo runtime de contêineres. Em geral, esse conjunto é bastante amplo e pode incluir muito mais recursos do que o aplicativo precisa. Vamos iniciar um pod no Kubernetes e ver quais recursos ele recebe.

Primeiro, vamos criar um YAML para implantar um Pod. Nesta configuração, usamos uma imagem do Docker Hub mantida pelo projeto original, baseada no Alpine e com a ferramenta capsh, que nos permite ver quais recursos nosso contêiner tem.

Observe que não definimos uma seção securityContext para esse contêiner, então todas as configurações serão os padrões do sistema. Lembre-se: se você não especificar um usuário no Kubernetes, o contêiner será executado como o usuário padrão definido no Dockerfile usado para criá-lo — que, no caso de muitos contêineres, é root.

% cat caps.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: caps
  labels:
    app: caps
spec:
  containers:
  - name: caps
    image: ollijanatuinen/capsh
    command: ["/bin/sleep", "3650d"]
    imagePullPolicy: IfNotPresent
  restartPolicy: Always

% kubectl apply -f caps.yaml 
pod/caps created

Quando o pod estiver em execução, podemos abrir um shell dentro dele e executar capsh para conferir os recursos:

% kubectl exec --stdin --tty caps -- ash 
/ # capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Remoção de recursos no securityContext do Kubernetes

Como podemos ver, por padrão, estamos executando como root e temos muitos recursos. Esse é o conjunto definido por padrão pelo runtime de contêineres. Agora, vamos tentar remover um desses recursos nas configurações de securityContext: vamos remover a capacidade de criar nós do sistema de arquivos, que corresponde a CAP_MKNOD:

matt@Mattbook capabilities_testing % cat dropcaps.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: dropcaps
  labels:
    app: dropcaps
spec:
  containers:
  - name: dropcaps
    image: ollijanatuinen/capsh
    command: ["/bin/sleep", "3650d"]
    imagePullPolicy: IfNotPresent
    securityContext:
      capabilities:
        drop: ["MKNOD"]
  restartPolicy: Always

% kubectl apply -f dropcaps.yaml 
pod/dropcaps created

% kubectl exec --stdin --tty dropcaps -- ash
/ # capsh --print
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_audit_write,cap_setfcap+eip
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Comparando com o primeiro pod, podemos ver que este pod agora não tem mais o recurso cap_mknod. Além de remover recursos individuais, também podemos remover todos eles pelo securityContext:

matt@Mattbook capabilities_testing % cat nocaps.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nocaps
  labels:
    app: nocaps
spec:
  containers:
  - name: nocaps
    image: ollijanatuinen/capsh
    command: ["/bin/sleep", "3650d"]
    imagePullPolicy: IfNotPresent
    securityContext:
      capabilities:
        drop:
          - ALL
  restartPolicy: Always

matt@Mattbook capabilities_testing % kubectl apply -f nocaps.yaml 
pod/nocaps created

matt@Mattbook capabilities_testing % kubectl exec --stdin --tty nocaps -- ash
/ # capsh --print
Current: =
Bounding set =
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Sem nenhum recurso, algumas funções do sistema falharão quando tentarmos executá-las. Por exemplo, vamos tentar instalar o bash usando apk:

/ # apk add bash
(1/5) Installing ncurses-terminfo-base (6.1_p20180818-r1)
(2/5) Installing ncurses-terminfo (6.1_p20180818-r1)
(3/5) Installing ncurses-libs (6.1_p20180818-r1)
(4/5) Installing readline (7.0.003-r0)
(5/5) Installing bash (4.4.19-r1)
Executing bash-4.4.19-r1.post-install
ERROR: bash-4.4.19-r1.post-install: script exited with error 127
Executing busybox-1.28.4-r1.trigger
ERROR: busybox-1.28.4-r1.trigger: script exited with error 127
1 error; 13 MiB in 19 packages

Mesmo que nosso contêiner esteja sendo executado como root, a instalação do pacote bash falha porque precisa definir permissões no sistema de arquivos, e removemos esse recurso do contêiner. Se quisermos impedir que alguém instale software no contêiner, gerenciar essas permissões por meio de recursos é uma das opções.

Embora o capsh ofereça uma forma bem organizada de ver quais recursos nosso contêiner tem, essa não é a única maneira de descobrir quais estão disponíveis. Também podemos acessar essas informações diretamente pelo sistema de arquivos proc, sem instalar nenhum software adicional:

/ # cd /proc/1/
/proc/1 # cat status 
----truncated
CapPrm:0000000000000000
CapEff:0000000000000000
CapBnd:0000000000000000
CapAmb:0000000000000000
----truncated

No arquivo /proc/1/status, os recursos são exibidos como um bitmap. Como nenhum recurso está habilitado neste contêiner, todos os valores são zero. Se consultarmos o mesmo arquivo no contêiner original, com todos os recursos habilitados:

/ # cd /proc/1/
/proc/1 # cat status 
----truncated
CapInh:00000000a80425fb
CapPrm:00000000a80425fb
CapEff:00000000a80425fb
CapBnd:00000000a80425fb
----truncated

Não é tão fácil de ler quanto a saída do capsh, mas cada bit exibido representa um recurso específico, conforme definido no cabeçalho correspondente do kernel.

Saiba mais sobre como melhorar a segurança do Kubernetes removendo os recursos padrão de um contêiner.

Adição de recursos no securityContext do Kubernetes

Além de remover recursos, também podemos adicioná-los novamente. Vamos retomar o exemplo anterior e adicionar um único recurso às configurações de securityContext: o recurso CAP_MKNOD:

% cat addcaps.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: addcaps
  labels:
    app: addcaps
spec:
  containers:
  - name: addcaps
    image: ollijanatuinen/capsh
    command: ["/bin/sleep", "3650d"]
    imagePullPolicy: IfNotPresent
    securityContext:
      capabilities:
        drop:
          - ALL
        add: ["MKNOD"]
  restartPolicy: Always

% kubectl create -f addcaps.yaml           
pod/addcaps created
% kubectl exec --stdin --tty addcaps -- ash
/ # capsh --print
Current: = cap_mknod+eip
Bounding set =cap_mknod
Securebits: 00/0x0/1'b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Neste exemplo, podemos ver que agora temos apenas esse recurso habilitado.

Princípio do menor privilégio no securityContext do Kubernetes

Neste artigo, vimos como as capacidades funcionam nos contêineres, como são configuradas no securityContext do Kubernetes e como são controladas pelo runtime de contêineres.

Seguindo o princípio do menor privilégio, a melhor prática de segurança é fornecer apenas os recursos de que o contêiner realmente precisa. Explorei esse assunto em uma publicação anterior. Pode ser surpreendente saber que a maioria dos processos não precisa de nenhum recurso do kernel, pois, mesmo quando precisam de permissões elevadas, isso pode ser controlado principalmente com permissões no nível dos arquivos.

Comece removendo todos os recursos no securityContext e, em seguida, adicione apenas os necessários. Para depurar falhas, consulte a saída de ferramentas como o SELinux e identifique quais recursos podem estar causando o problema. Também é importante lembrar que os contêineres do Kubernetes podem ser executados como root, a menos que você especifique outro usuário.

Como aplicar as configurações de recursos do securityContext no Kubernetes

Para garantir que as configurações do securityContext, como os recursos e a execução como usuário não root, estejam definidas, podemos usar controladores de admissão no cluster do Kubernetes. Assim, os contêineres não serão iniciados sem as configurações de segurança adequadas. O Kubernetes inclui o controlador PodSecurityPolicy, que permite aplicar configurações do securityContext.

No entanto, observe que esse recurso será descontinuado na versão 1.21, em favor de projetos mantidos externamente, como o Open Policy Agent.

Também precisamos incorporar ao nosso processo de desenvolvimento a visibilidade e a correção desse tipo de configuração de segurança. O Snyk pode analisar seus arquivos YAML do Kubernetes, detectar configurações inseguras de recursos no securityContext e outros problemas de configuração, além de oferecer recomendações de correção diretamente nos fluxos de trabalho dos desenvolvedores. Esse recurso está disponível pela Snyk CLI e também pode ser integrado diretamente a sistemas de gerenciamento de código-fonte e de integração contínua:

% snyk iac test caps.yaml

Testing caps.yaml...

Infrastructure as code issues:
  ✗ Container is running with default set of capabilities [Medium Severity] [SNYK-CC-K8S-6] in Deployment
    introduced by input > spec > containers[caps] > securityContext > capabilities > drop

  ✗ Container is running without root user control [Medium Severity] [SNYK-CC-K8S-10] in Deployment
    introduced by input > spec > containers[caps] > securityContext > runAsNonRoot

  ✗ Container is running without memory limit [Low Severity] [SNYK-CC-K8S-4] in Deployment
    introduced by input > spec > containers[caps] > resources > limits > memory

  ✗ Container is running without cpu limit [Low Severity] [SNYK-CC-K8S-5] in Deployment
    introduced by input > spec > containers[caps] > resources > limits > cpu

  ✗ Container is running with writable root filesystem [Low Severity] [SNYK-CC-K8S-8] in Deployment
    introduced by input > spec > containers[caps] > securityContext > readOnlyRootFilesystem

  ✗ Container is running without AppArmor profile [Low Severity] [SNYK-CC-K8S-32] in Deployment
    introduced by metadata > annotations['container.apparmor.security.beta.kubernetes.io/caps']

  ✗ Container is running without liveness probe [Low Severity] [SNYK-CC-K8S-41] in Deployment
    introduced by spec > containers[caps] > livenessProbe

  ✗ Container could be running with outdated image [Low Severity] [SNYK-CC-K8S-42] in Deployment
    introduced by spec > containers[caps] > imagePullPolicy

Organization:      matt-jarvis-snyk
Type:              Kubernetes
Target file:       caps.yaml
Project name:      capabilities_testing
Open source:       no
Project path:      caps.yaml

Tested caps.yaml for known issues, found 8 issues
Exemplo de código de contexto de segurança do Kubernetes mostrando um contêiner configurado com privileged: true e recursos padrão

Quer experimentar esse recurso? Crie uma conta gratuita hoje mesmo!

Comece a participar de desafios de Capture the Flag

Aprenda a resolver desafios de Capture the Flag assistindo sob demanda ao nosso workshop virtual introdutório.