Skip to main content

Security Context

En CKS necesitamos profundizar más en este tema. Haz un overview en cka security context.

Security context permite definir privilegios y control de acceso a nivel de pod o a nivel de contenedor.

Podemos especificar:

  • userID
  • groupID
  • Escalada de privilegios
  • Linux capabilities
  • otros
spec:
#Nivel de pod (Aplicado en todos los contenedores)
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000

containers:
- name: busybox
command: ["sleep","3600"]
image: busybox
#Nivel de contenedor
securityContext:
runAsUser: 0 # Va a sobrescribir el user en este pod
#
#runAsUser: 0
#runAsGroup: 3000
#fsGroup: 2000
- name: busybox-2
command: ["sleep","3600"]
image: busybox2
#Esto será heredado de la definición de pods
#securityContext:
#runAsUser: 1000
#runAsGroup: 3000
#fsGroup: 2000

En esta definición estamos diciendo que:

  • runAsUser -> uid=1000 (user ID)
  • runAsGroup -> gid=3000 (main group ID)
  • fsGroup -> groups=2000 (supplementary group ID)

A partir de Kubernetes 1.25 fue introducido en el spec del pod el campo de sistema operativo (os) con los valores para name pudiendo ser windows o linux (predeterminado). Este campo indicará en cuál sistema operativo el pod será ejecutado. Además de ser útil en el futuro para el kube-scheduler también tendrá funcionalidad en el security context.

spec:
os:
name: windows
containers:
- name: windows-container
image: mcr.microsoft.com/windows/servercore:ltsc2022

Existen parámetros que son posibles existir a nivel de pod, a nivel de contenedor o en ambos. Vamos a hacer un análisis rápido solo para saber qué es posible. Nadie necesita guardar esto para el CKS.

Una tabla general rápida en la cual hablaremos solamente de los principales.

ParámetroTipoOSPod LevelContainer LevelDescripción
allowPrivilegeEscalationbooleanLinuxNoControla si el proceso puede ganar más privilegios que quien lo lanzó. Es automáticamente definido como true en caso de "privileged true" o tener una capability CAP_SYS_ADMIN.
appArmorProfileAppArmorProfileLinuxSi está seteado el appArmorProfile del pod será alterado
capabilitiesCapabilitiesLinuxNoPara adicionar o dropar capabilities en el contenedor.
fsGroupIntegerLinuxNoUn supplemental group que será aplicado en todos los contenedores del pod.
privilegedbooleanLinuxNoPara ejecutar el contenedor como root garantizando privilegios equivalentes al root del host. El predeterminado es falso.
procMountstringLinuxNoEnvuelve el tipo de proc mount usado por el contenedor.
readOnlyRootFilesystembooleanLinuxNoSi /root del contenedor debe ser solamente lectura. El predeterminado es falso
runAsGroupintegerLinuxEl gid para ejecutar el entrypoint del proceso.
runAsNonRootbooleanLinuxIndica que el contenedor necesita ejecutar con un usuario que no es root. El Kubelet valida si la imagen está seteando el usuario.
runAsUserintegerLinuxUID del usuario. El predeterminado es el mismo utilizado en la imagen del contenedor.
seLinuxOptionsSELinuxOptionsLinuxSi no está especificado, el container runtime irá a alocar un SELinux aleatorio para cada contenedor.
seccompProfileSeccompProfileLinuxOpciones del seccomp para los contenedores.
supplementalGroupsinteger arrayLinuxNoUna lista de GID aplicados al primer proceso de cada contenedor.
supplementalGroupsPolicystringLinuxNoUsado solamente si supplementalGroups está definido.
sysctlssysctls arrayLinuxNoContiene una lista de sysctls con namespaces usados por el pod.
windowsOptionsWindowsSecurityContextOptionswindowsConfiguraciones específicas de Windows aplicadas a todos los contenedores.

Vamos a usar este yaml como base y vamos a alterarlo varias veces.

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 y runAsGroup (pod y container levels)

  • La imagen busybox utiliza el usuario root en la propia imagen y no pasamos nada para alterarlo.
  • Si creamos un archivo será creado de acuerdo con el usuario que estamos usando que en ese caso es 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 a definir un usuario para el pod, que será heredado por el contenedor pues no sobrescribiremos.

  • Alteramos el usuario del pod.
  • Como la imagen define el workdir directo para /, como el usuario 1000 no tenemos permiso para crear nada.
  • Si alteramos para un local como tmp que todos los usuarios tienen permiso podemos crear y el archivo creado pertenecerá al usuario especificado.
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 y container Level)

Ahora vamos a forzar el contenedor a ejecutar como no root, pero no pasaremos ningún user. Si la imagen del contenedor ya define un usuario no tendremos problemas, pero si define como root no podrá ser ejecutada.

  • En este escenario mantenemos el user y no tenemos problemas pues estamos alterando el usuario dueño del proceso principal.
  • Observe que el dueño del proceso 1 es el user 1000 que mantenemos.
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

Pero removiendo el usuario tenemos el problema arriba citado.

  • En este caso estamos forzando que la imagen tenga un usuario definido para el proceso, lo que no es el caso de 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

La imagen de nginx predeterminada utiliza root, pero existe otra imagen que no utiliza, vamos a usarla para prueba

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 defecto los contenedores se ejecutan como unprivileged, pero es posible ejecutar como privileged.

Un caso que esto podría suceder es si quisiéramos ejecutar un docker-in-docker en el contenedor, o sea, un contenedor dentro del otro. Podríamos también tener un contenedor que necesita de acceso a todos los devices.

Ejecutar un contenedor como privileged quiere decir que el user 0 (root) del contenedor es directamente mapeado para el user 0 (root) del host. Una de las abstracciones del uso de contenedor es que dentro del contenedor podemos tener el mismo id de un usuario del host o de otros contenedor pero son diferentes, ellos pueden tener el mismo id pero permisos diferentes.

Con el comando sysctl podemos setear parámetros del kernel en tiempo de ejecución, pero para eso necesitamos permiso de root.

Sin privileged, aun siendo root en el contenedor no 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 en privileged true. Recordando que privileged es un security context a nivel de contenedor solamente.

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: {}

# Solo para constar, el hostname es el propio nombre del pod (en este caso pod)
# El comando sysctl cambia en proc de forma temporal no en /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

# En el worker donde el pod está ejecutando la alteración a nivel de kernel pero en el kernel group del pod y no en el host. No es el mismo 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)

Ahora vamos a hablar sobre allowPrivilegeEscalation que es automáticamente true.

El recurso allowPrivilegeEscalation en Kubernetes es una configuración de seguridad que controla si un proceso dentro de un contenedor puede obtener privilegios adicionales, como a través del comando sudo o al usar setuid binaries.

Cómo funciona:

  • allowPrivilegeEscalation: true (default): Permite que procesos dentro del contenedor escalen sus privilegios. Esto puede ser necesario para algunas aplicaciones que necesitan elevar sus privilegios temporalmente para ejecutar ciertas operaciones.

  • allowPrivilegeEscalation: false: Bloquea la elevación de privilegios. Aun que el contenedor sea ejecutado con privilegios de root, no podrá usar mecanismos como sudo o setuid para ganar privilegios adicionales. Esta configuración es usada como una capa extra de seguridad para limitar las capabilities de los procesos dentro del contenedor.

Relación con privileged y runAsNonRoot:

  • privileged: Si el contenedor está en modo privileged: true, ignora la configuración de allowPrivilegeEscalation porque ya tiene privilegios totales en el host.

  • runAsNonRoot: Si runAsNonRoot: true está configurado, allowPrivilegeEscalation generalmente debe ser false, pues el objetivo es garantizar que el contenedor no tenga acceso root o capacidad de escalarlo.

NoNewPrivs 0 significa que está desactivado, o sea, puede elevar los privilegios.

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:
#Este ya es el default, es solo 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 a alterar para false NoNewPrivs debe ser 1 mostrando que está activado.

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 y SecComp

Haz un overview sobre esos dos recursos que tenemos en Linux.

Este contenido, a pesar de cobrado en CKS fue disponibilizado en apparmor, seccomp.

Es necesario el estudio de ambas herramientas.