En la entrada anterior KubeLinter: identifica malas configuraciones en los objetos de Kubernetes, KubeLinter identificaba dos errores que se solucionan usando las opciones: runAsUser
y readOnlyRootFilesystem
.
En esta entrada comparo los efectos de aplicar una u otra, así como qué pasa cuando se aplican las dos al mismo tiempo.
La definición del Pod es:
---
kind: Pod
apiVersion: v1
metadata:
name: jumpod
spec:
containers:
- name: busybox
image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "64Mi"
cpu: "10m"
restartPolicy: Always
Al analizar este fichero de definición con KubeLinter, obtenemos los errores:
jumpod.yaml: (object: <no namespace>/jumpod /v1, Kind=Pod) container "busybox" does not have a read-only root file system (check: no-read-only-root-fs, remediation: Set readOnlyRootFilesystem to true in your container's securityContext.)
jumpod.yaml: (object: <no namespace>/jumpod /v1, Kind=Pod) container "busybox" is not set to runAsNonRoot (check: run-as-non-root, remediation: Set runAsUser to a non-zero number, and runAsNonRoot to true, in your pod or container securityContext. See https://kubernetes.io/docs/tasks/configure-pod-container/security-context/ for more details.)
Error: found 2 lint errors
Filtrando la salida anterior:
(...) container "busybox" does not have a read-only root file system (...)
(...) container "busybox" is not set to runAsNonRoot (...)
El primer aviso apunta a que la solución pasa por especificar un root file system en modo lectura. El segundo hace referencia al usuario con el que se ejecuta el contenedor (que no debería ser el usuario root).
Para solucionar el primer error, especificamos readOnlyRootFilesystem: true
y para el segundo, especificamos runAsUser: 1001
:
---
kind: Pod
apiVersion: v1
metadata:
name: jumpod
spec:
containers:
- name: busybox
image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 1001
runAsGroup: 1001
readOnlyRootFilesystem: true
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "64Mi"
cpu: "10m"
restartPolicy: Always
Comparando el efecto de estas opciones
Primer caso: runAsUser: 1001
Abrimos una shell en el Pod desplegado con el YAML donde se indica que el contenedor debe ejecutarse como usuario 1001:
---
kind: Pod
apiVersion: v1
metadata:
name: jumpod
spec:
containers:
- name: busybox
image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 1001
runAsGroup: 1001
# readOnlyRootFilesystem: true
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "64Mi"
cpu: "10m"
restartPolicy: Always
kubectl exec -it pod/jumpod -n jumpod -- /bin/sh
En el Pod:
/ $ whoami
whoami: unknown uid 1001
Si revisamos a qué tiene acceso el usuario en el contenedor, vemos que sólo tiene permisos para escribir en /tmp
, mientras que en el resto sólo tiene permisos de lectura y de ejecución: drwxr-xr-x
(el propietario es root
):
/ $ ls -lah
total 44K
drwxr-xr-x 1 root root 4.0K Feb 12 18:47 .
drwxr-xr-x 1 root root 4.0K Feb 12 18:47 ..
drwxr-xr-x 2 root root 12.0K Feb 1 19:44 bin
drwxr-xr-x 5 root root 360 Feb 12 18:47 dev
drwxr-xr-x 1 root root 4.0K Feb 12 18:47 etc
drwxr-xr-x 2 nobody nobody 4.0K Feb 1 19:44 home
dr-xr-xr-x 368 root root 0 Feb 12 18:47 proc
drwx------ 2 root root 4.0K Feb 1 19:44 root
dr-xr-xr-x 13 root root 0 Feb 12 18:47 sys
drwxrwxrwt 1 root root 4.0K Feb 12 18:50 tmp
drwxr-xr-x 3 root root 4.0K Feb 1 19:44 usr
drwxr-xr-x 1 root root 4.0K Feb 12 18:47 var
/ $ touch /tmp/test
/ $ ls /tmp/
/tmp/test
El usuario con uid: 1001
tiene permisos sobre /tmp
y puede crear ficheros en esa carpeta. En el resto, no tiene permisos de escritura.
Segundo caso: readOnlyRootFilesystem: true
En el segundo caso, si especficamos la opción readOnlyRootFilesystem: true
, el usuario con el que se ejecutar el Pod es root
:
---
kind: Pod
apiVersion: v1
metadata:
name: jumpod
spec:
containers:
- name: busybox
image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
securityContext:
#runAsUser: 1001
#runAsGroup: 1001
readOnlyRootFilesystem: true
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "64Mi"
cpu: "10m"
restartPolicy: Always
/ # whoami
root
Aunque el usuario tiene permisos de escritura sobre el sistema de ficheros del Pod, cuando intentamos crear un fichero:
/ # ls -lah
total 44K
drwxr-xr-x 1 root root 4.0K Feb 12 19:17 .
drwxr-xr-x 1 root root 4.0K Feb 12 19:17 ..
drwxr-xr-x 2 root root 12.0K Feb 1 19:44 bin
drwxr-xr-x 5 root root 360 Feb 12 19:17 dev
drwxr-xr-x 1 root root 4.0K Feb 12 19:17 etc
drwxr-xr-x 2 nobody nobody 4.0K Feb 1 19:44 home
dr-xr-xr-x 375 root root 0 Feb 12 19:17 proc
drwx------ 2 root root 4.0K Feb 1 19:44 root
dr-xr-xr-x 13 root root 0 Feb 12 19:17 sys
drwxrwxrwt 2 root root 4.0K Feb 1 19:44 tmp
drwxr-xr-x 3 root root 4.0K Feb 1 19:44 usr
drwxr-xr-x 1 root root 4.0K Feb 12 19:17 var
/ # touch /bin/test
touch: /bin/test: Read-only file system
Tercer caso: runAsUser: 1001
y readOnlyRootFilesystem: true
Si desplegamos un Pod en el que especificamos un usuario no-root (uid: 1001
) y además un sistema de ficheros de sólo lectura:
---
kind: Pod
apiVersion: v1
metadata:
name: jumpod
spec:
containers:
- name: busybox
image: busybox
command:
- sleep
- "3600"
imagePullPolicy: IfNotPresent
securityContext:
runAsUser: 1001
runAsGroup: 1001
readOnlyRootFilesystem: true
resources:
requests:
memory: "64Mi"
cpu: "10m"
limits:
memory: "64Mi"
cpu: "10m"
restartPolicy: Always
/ $ whoami
whoami: unknown uid 1001
/ $ ls -lah
total 44K
drwxr-xr-x 1 root root 4.0K Feb 12 19:33 .
drwxr-xr-x 1 root root 4.0K Feb 12 19:33 ..
drwxr-xr-x 2 root root 12.0K Feb 1 19:44 bin
drwxr-xr-x 5 root root 360 Feb 12 19:33 dev
drwxr-xr-x 1 root root 4.0K Feb 12 19:33 etc
drwxr-xr-x 2 nobody nobody 4.0K Feb 1 19:44 home
dr-xr-xr-x 373 root root 0 Feb 12 19:33 proc
drwx------ 2 root root 4.0K Feb 1 19:44 root
dr-xr-xr-x 13 root root 0 Feb 12 19:33 sys
drwxrwxrwt 2 root root 4.0K Feb 1 19:44 tmp
drwxr-xr-x 3 root root 4.0K Feb 1 19:44 usr
drwxr-xr-x 1 root root 4.0K Feb 12 19:33 var
/ $ touch /tmp/test
touch: /tmp/test: Read-only file system
En este caso, aunque el usuario uid: 1001
tiene permisos de escritura en /tmp
, como el sistema de ficheros es readOnly, no puede escribir en el sistema de ficheros.
Conclusión
Configurar el root volume filesystem como sólo lectura es la opción que puede tener un mayor impacto en el funcionamiento de la aplicación. Al activar esta configuración ningún usuario (incluyendo al usuario root) puede escribir en el sistema de ficheros raíz.
Si especificamos sólo readOnlyRootFilesystem: true
el contenedor sigue ejecutándose como root, lo que va en contra de las buenas prácticas. Por tanto, también es recomendable usar la opción de runAsUser
para usar un usuario con menos privilegios.
Una solución de compromiso podría ser usar sólo runAsUser
con un usuario no root; de esta forma el sistema de permisos de Linux permite limitar dónde puede escribir el proceso iniciado por el usuario indicado, mientras que permite el acceso a carpetas como /tmp
que algunos procesos pueden utilizar por defecto.