Security Context
No CKS precisamos nos aprofundar mais nesse tema. Faça um overview em cka security context.
Security context permite definirmos privilégios e controle de acesso em nível de pod ou em nível de container.
Podemos especificar:
- userID
- groupID
- Escalada de privilégios
- Linux capabilities
- outros
spec:
#Nível de pod (Aplicado em todos os containers)
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
containers:
- name: busybox
command: ["sleep","3600"]
image: busybox
#Nível de container
securityContext:
runAsUser: 0 # Vai sobrescrever o user neste pod
#
#runAsUser: 0
#runAsGroup: 3000
#fsGroup: 2000
- name: busybox-2
command: ["sleep","3600"]
image: busybox2
#Isso será herdado da definição de pods
#securityContext:
#runAsUser: 1000
#runAsGroup: 3000
#fsGroup: 2000
Nessa definição estamos dizendo que:
- runAsUser -> uid=1000 (user ID)
- runAsGroup -> gid=3000 (main group ID)
- fsGroup -> groups=2000 (suplementary group ID)
A partir do Kubernetes 1.25 foi introduzido no spec do pod o campo de sistema operacional (os) com os valores para name podendo ser windows ou linux (padrão). Este campo indicará em qual sistema operacional o pod será executado. Além de ser útil no futuro para o kube-scheduler também terá funcionalidade no security context.
spec:
os:
name: windows
containers:
- name: windows-container
image: mcr.microsoft.com/windows/servercore:ltsc2022
Existem parâmetros que são possíveis existir em nível de pod, em nível de container ou em ambos. Vamos fazer uma análise rápida só para saber o que é possível. Ninguém precisa guardar isso para o CKS.
Uma tabela geral rápida no qual falaremos somente dos principais.
Parâmetro | Tipo | OS | Pod Level | Container Level | Descrição |
---|---|---|---|---|---|
allowPrivilegeEscalation | boolean | Linux | Não | Sim | Controla se o processo pode ganhar mais privilégios do que quem o lançou. É automaticamente definido como true em caso de "privileged true" ou tiver uma capability CAP_SYS_ADMIN. |
appArmorProfile | AppArmorProfile | Linux | Sim | Sim | Se setado o appArmorProfile do pod será alterado |
capabilities | Capabilities | Linux | Não | Sim | Para adicionar ou dropar capabilities no container. |
fsGroup | Integer | Linux | Sim | Não | Um supplemental group que será aplicado em todos os containers do pod. |
privileged | boolean | Linux | Não | Sim | Para rodar o container como root garantindo privilégios equivalentes ao root do host. O padrão é falso. |
procMount | string | Linux | Não | Sim | Envolve o tipo de proc mount usado pelo container. |
readOnlyRootFilesystem | boolean | Linux | Não | Sim | Se /root do container deve ser somente leitura. O padrão é falso |
runAsGroup | integer | Linux | Sim | Sim | O gid para executar o entrypoint do processo. |
runAsNonRoot | boolean | Linux | Sim | Sim | Indica que o container precisa rodar com um usuário que não é root. O Kubelet valida se a imagem esta setando o usuário. |
runAsUser | integer | Linux | Sim | Sim | UID do usuário. O padrão é o mesmo utilizado na imagem do container. |
seLinuxOptions | SELinuxOptions | Linux | Sim | Sim | Se não especificado, o container runtime irá alocar um SELinux randomico para cada container. |
seccompProfile | SeccompProfile | Linux | Sim | Sim | Opções do seccomp para os containers. |
supplementalGroups | integer array | Linux | Sim | Não | Uma lista de GID aplicados ao primeiro processo de cada container. |
supplementalGroupsPolicy | string | Linux | Sim | Não | Usado somente se supplementalGroups for definido. |
sysctls | sysctls array | Linux | Sim | Não | Contém uma lista de sysctls com namespaces usados pelo pod. |
windowsOptions | WindowsSecurityContextOptions | windows | Sim | Sim | Configurações específicas do Windows aplicadas a todos os contêineres. |
Vamos usar esse yaml como base e vamos alterá-lo várias vezes.
root@cks-master:~# k run pod --image=busybox --command -oyaml --dry-run=client -- sh -c 'sleep 1d' > pod.yaml
root@cks-master:~# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
containers:
- command:
- sh
- -c
- sleep 1d
image: busybox
name: pod
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~#
runAsUser e runAsGroup (pod e container levels)
- A imagem busybox utiliza utiliza o usuário root na própria image e não passamos nada para alterá-lo.
- Se criamos um arquivo ele será criado de acordo com o usuário que estamos usando que nesse caso é o root.
root@cks-master:~# k apply -f pod.yaml
pod/pod created
root@cks-master:~# k exec -it pod -- sh
/ # id
uid=0(root) gid=0(root) groups=10(wheel)
/ # touch test
/ # ls -lh test
-rw-r--r-- 1 root root 0 Aug 29 12:00 test
/ # exit
root@cks-master:~# k delete pod pod --force --grace-period 0
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "pod" force deleted
Vamos definir um usuário para o pod, que será herdado pelo container pois não sobreescreveremos.
- Alteramos o usuário do pod.
- Como a imagem define o workdir direto para o /, como o usuário 1000 não temos permissão para criar nada.
- Se alteramos para um local como o tmp que todos os usuários tem permissão podemos criar e o arquivo criado pertencerá ao usuário específicado.
root@cks-master:~# vim pod.yaml
root@cks-master:~# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
containers:
- command:
- sh
- -c
- sleep 1d
image: busybox
name: pod
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~# k apply -f pod.yaml
pod/pod created
root@cks-master:~# k exec -it pod -- sh
~ $ id
uid=1000 gid=3000
~ $ touch test
touch: test: Permission denied
~ $ pwd
/
~ $ cd tmp/
/tmp $ touch test
/tmp $ ls -lh test
-rw-r--r-- 1 1000 3000 0 Aug 29 12:07 test
/tmp $ exit
root@cks-master:~# k delete pod pod --force --grace-period 0
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
runAsNonRoot (pod e container Level)
Agora vamos forçar o container rodar como não root, mas não passaremos nenhum user. Se a imagem do container já define um usuário não teremos problemas, mas se define como root não poderá ser executada.
- Nesse cenário mantemos o user e não temos problemas pois estamos alterando o usuário dono do processo principal.
- Observe que o dono do processo 1 é o user 1000 que mantemos.
root@cks-master:~# vim pod.yaml
root@cks-master:~# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
containers:
- command:
- sh
- -c
- sleep 1d
image: busybox
name: pod
resources: {}
securityContext:
runAsNonRoot: true
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~# k apply -f pod.yaml
pod/pod created
root@cks-master:~# k get pods
NAME READY STATUS RESTARTS AGE
pod 1/1 Running 0 5s
root@cks-master:~# k exec -it pod -- sh
~ $
~ $ ps
PID USER TIME COMMAND
1 1000 0:00 sh -c sleep 1d
8 1000 0:00 sh
14 1000 0:00 ps
~ $ exit
root@cks-master:~# k delete pod pod --force --grace-period 0
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "pod" force deleted
Porém removendo o usuário temos o problema acima citado.
- Nesse caso estamos forçando que a imagem tenha um usuário definido para o processo, o que não é o caso do busybox.
root@cks-master:~# vim pod.yaml
root@cks-master:~# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
# securityContext:
# runAsUser: 1000
# runAsGroup: 3000
containers:
- command:
- sh
- -c
- sleep 1d
image: busybox
name: pod
resources: {}
securityContext:
runAsNonRoot: true
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~# k apply -f pod.yaml
pod/pod created
root@cks-master:~# k get pod
NAME READY STATUS RESTARTS AGE
pod 0/1 CreateContainerConfigError 0 3s
root@cks-master:~# k get pod pod -o jsonpath={.status.containerStatuses.*.state} | jq
{
"waiting": {
"message": "container has runAsNonRoot and image will run as root (pod: \"pod_default(7dd567c0-ead9-4460-a012-35acd9122bad)\", container: pod)",
"reason": "CreateContainerConfigError"
}
}
root@cks-master:~# k delete pod pod
pod "pod" deleted
A imagem do nginx padrão utiliza o root, mas existe uma outra imagem que não utiliza, vamos usá-la para teste
root@cks-master:~# k run nginx --image=nginxinc/nginx-unprivileged -o yaml --dry-run=client > podnonroot.yaml
root@cks-master:~# vim podnonroot.yaml
root@cks-master:~# cat podnonroot.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: nginx
name: nginx
spec:
containers:
- image: nginxinc/nginx-unprivileged
name: nginx
resources: {}
securityContext:
runAsNonRoot: true
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~# k apply -f podnonroot.yaml
pod/nginx created
root@cks-master:~# k exec -it nginx -- bash
nginx@nginx:/$ id
uid=101(nginx) gid=101(nginx) groups=101(nginx)
nginx@nginx:/$ exit
exit
root@cks-master:~# k delete pod nginx
pod "nginx" deleted
privileged (Container Level)
Por padrão os containers rodam como unprivileged, mas é possível rodar como privileged.
Um caso que isso poderia acontecer é se quisessemos rodar um docker-in-docker no container, ou seja, um container dentro do outro. Poderíamos também ter um container que precisa de acesso a todos os devices.
Rodar um container como privileged quer dizer que o user 0 (root) do container é diretamente mapeado para o user 0 (root) do host. Uma das abstrações do uso de container é que dentro do container podemos ter o mesmo id de um usuário do host ou de outros container mas são diferentes, eles podem ter o mesmo id mas permissões diferentes.
Com o comando sysctl podemos setar parâmetros do kernel em tempo de execução, mas para isso precisamos de permissão de root.
Sem o privileged, mesmo sendo root no container não conseguimos alterar.
root@cks-master:~# vim pod.yaml
root@cks-master:~# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
containers:
- command:
- sh
- -c
- sleep 1d
image: busybox
name: pod
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~# k apply -f pod.yaml
pod/pod created
root@cks-master:~# k exec -it pod -- bash
root@cks-master:~# k exec -it pod -- sh
/ # id
uid=0(root) gid=0(root) groups=10(wheel)
/ # sysctl kernel.hostname=cks
sysctl: error setting key 'kernel.hostname': Read-only file system
/ # sysctl kernel.hostname=cks
Colocando em privileged true. Lembrando que privileged é um security context em nível de container somente.
root@cks-master:~# vim pod.yaml
root@cks-master:~# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
containers:
- command:
- sh
- -c
- sleep 1d
image: busybox
name: pod
resources: {}
securityContext:
privileged: true
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
# Só para constar, o hostname é o próprio nome do pod (neste caso pod)
# O comando sysctl muda em proc de forma temporária não em /etc/hostname.
root@cks-master:~# k exec pod -it -- sh
/ # cat /proc/sys/kernel/hostname
pod
/ # sysctl kernel.hostname=cks-test
kernel.hostname = cks-test
/ # cat /proc/sys/kernel/hostname
cks-test
/ # cat /etc/hostname
pod
/ # exit
# No worker onde o pod esta rodando a alteração em nível de kernel mas no kernel group do pod e não no host. Não é o mesmo filesystem.
root@cks-worker:~# cat /proc/sys/kernel/hostname
cks-worker
root@cks-master:~# k delete pod pod --force --grace-period 0
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "pod" force deleted
allowPrivilegeEscalation (Container Level)
Agora vamos falar sobre allowPrivilegeEscalation que é automaticamente true.
O recurso allowPrivilegeEscalation no Kubernetes é uma configuração de segurança que controla se um processo dentro de um container pode obter privilégios adicionais, como através do comando sudo ou ao usar setuid binaries.
Como funciona:
-
allowPrivilegeEscalation: true
(default): Permite que processos dentro do container escalem seus privilégios. Isso pode ser necessário para algumas aplicações que precisam elevar seus privilégios temporariamente para executar certas operações. -
allowPrivilegeEscalation: false
: Bloqueia a elevação de privilégios. Mesmo que o container seja executado com privilégios de root, ele não poderá usar mecanismos como sudo ou setuid para ganhar privilégios adicionais. Essa configuração é usada como uma camada extra de segurança para limitar as capabilities dos processos dentro do container.
Relação com privileged e runAsNonRoot:
-
privileged
: Se o container estiver em modoprivileged: true
, ele ignora a configuração de allowPrivilegeEscalation porque já tem privilégios totais no host. -
runAsNonRoot
: SerunAsNonRoot: true
estiver configurado,allowPrivilegeEscalation geralmente deve ser false
, pois o objetivo é garantir que o container não tenha acesso root ou capacidade de escalá-lo.
NoNewPrivs 0 significa que esta desativado, ou seja, pode elevar os privilégios.
root@cks-master:~# cat pod.yaml
root@cks-master:~# vim pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
containers:
- command:
- sh
- -c
- sleep 1d
image: busybox
name: pod
resources: {}
securityContext:
#Esse já é o default, é só para confirmar
allowPrivilegeEscalation: true
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~# k apply -f pod.yaml
pod/pod created
root@cks-master:~# k exec -it pod -- sh
/ #
/ # cat /proc/1/status | grep NoNewPrivs
NoNewPrivs:0
/ # exit
root@cks-master:~# k delete pod pod --force --grace-period 0
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "pod" force deleted
Vamos alterar para false NoNewPrivs deve ser 1 mostrando que esta ativado.
root@cks-master:~# vim pod.yaml
root@cks-master:~# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: pod
name: pod
spec:
containers:
- command:
- sh
- -c
- sleep 1d
image: busybox
name: pod
resources: {}
securityContext:
allowPrivilegeEscalation: false
dnsPolicy: ClusterFirst
restartPolicy: Always
status: {}
root@cks-master:~# k apply -f pod.yaml
pod/pod created
root@cks-master:~# k exec -it pod -- sh
/ #
/ # cat /proc/1/status | grep NoNewPrivs
NoNewPrivs:1
/ # exit
root@cks-master:~# k delete pod pod --force --grace-period 0
Warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.
pod "pod" force deleted
AppArmor e SecComp
Faça um overview sobre esses dois recursos que temos no Linux.
Esse conteúdo, apesar de cobrado no CKS foi disponibilizado em apparmor, seccomp.
É necessário o estudo de ambas as ferramentas.