Hashes
Podemos verificar a través de los hashes la integridad de las imágenes usadas por los contenedores.
Recomiendo la lectura de hashes antes de continuar.
Vamos a comparar el binario del apiserver con el binario dentro del contenedor. Estamos en un clúster de versión 1.30.3. Entonces debemos analizar todo en el documento https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.30.md#downloads-for-v1303.
root@cks-master:~# wget https://dl.k8s.io/v1.30.3/kubernetes-server-linux-amd64.tar.gz
--2024-08-20 17:43:28-- https://dl.k8s.io/v1.30.3/kubernetes-server-linux-amd64.tar.gz
Resolving dl.k8s.io (dl.k8s.io)... 34.107.204.206, 2600:1901:0:26f3::
Connecting to dl.k8s.io (dl.k8s.io)|34.107.204.206|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://cdn.dl.k8s.io/release/v1.30.3/kubernetes-server-linux-amd64.tar.gz [following]
--2024-08-20 17:43:28-- https://cdn.dl.k8s.io/release/v1.30.3/kubernetes-server-linux-amd64.tar.gz
Resolving cdn.dl.k8s.io (cdn.dl.k8s.io)... 151.101.193.55, 151.101.129.55, 151.101.65.55, ...
Connecting to cdn.dl.k8s.io (cdn.dl.k8s.io)|151.101.193.55|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 374416169 (357M) [application/x-tar]
Saving to: 'kubernetes-server-linux-amd64.tar.gz'
kubernetes-server-l 100%[===================>] 357.07M 89.4MB/s in 4.0s
2024-08-20 17:43:32 (89.9 MB/s) - 'kubernetes-server-linux-amd64.tar.gz' saved [374416169/374416169]
# Guardando el hash obtenido del enlace anterior en compare
root@cks-master:~# echo "67282a349bd203fcc8d5d1d59d5b82fc56a14ea66f5a769ef457177ac5bcfb2fb65c239503a68f06a256f8919521fc96b5aad563bfec74eec13afb79a174f96b" > compare
# Agregar el hash que tenemos también al archivo compare
root@cks-master:~# sha512sum kubernetes-server-linux-amd64.tar.gz | cut -d ' ' -f 1 >> compare
67282a349bd203fcc8d5d1d59d5b82fc56a14ea66f5a769ef457177ac5bcfb2fb65c239503a68f06a256f8919521fc96b5aad563bfec74eec13afb79a174f96b
# Si solo tenemos una línea entonces es el mismo.
root@cks-master:~# cat compare | uniq
67282a349bd203fcc8d5d1d59d5b82fc56a14ea66f5a769ef457177ac5bcfb2fb65c239503a68f06a256f8919521fc96b5aad563bfec74eec13afb79a174f96b
# Vamos a hacer el unzip para obtener el binario e ir hasta él
root@cks-master:~# tar xzvf kubernetes-server-linux-amd64.tar.gz
root@cks-master:~# cd kubernetes/server/bin/
# Obteniendo el sha512 de él y guardándolo
root@cks-master:~/kubernetes/server/bin# ls kube-apiserver
kube-apiserver
root@cks-master:~/kubernetes/server/bin# ./kube-apiserver --version
Kubernetes v1.30.3
root@cks-master:~/kubernetes/server/bin# sha256sum kube-apiserver | cut -d ' ' -f 1 > apiserver-sha512
Ya tenemos acceso al hash SHA256 del kube-apiserver, ahora necesitamos verificar si coincide con el hash del que estamos ejecutando.
# Si intentamos entrar al shell del contenedor en kube-apiserver no podemos porque no tiene shell.
root@cks-master:/etc/kubernetes/manifests# k -n kube-system exec pods/kube-apiserver-cks-master -- sh
error: Internal error occurred: Internal error occurred: error executing command in container: failed to exec in container: failed to start exec "adc8bb808392966cfe63f7f425e72ec33c3494ea2e1d8d477741abcd19a875ba": OCI runtime exec failed: exec failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown
# Si tenemos el número del proceso podemos verificar el filesystem del proceso y encontrar el binario que está ejecutando, ya que estamos en el nodo donde se está ejecutando.
# El número del proceso es 1479759
root@cks-master:~/kubernetes/server/bin# ps -aux | grep kube-apiserver
root 1479759 6.9 7.5 1541316 302980 ? Ssl 18:10 0:50 kube-apiserver --advertise-address=10.128.0.5 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --enable-bootstrap-token-auth=true --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt --etcd-certfile=/etc/kubernetes/pki/apiserver-etcd-client.crt --etcd-keyfile=/etc/kubernetes/pki/apiserver-etcd-client.key --etcd-servers=https://127.0.0.1:2379 --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --secure-port=6443 --service-account-issuer=https://kubernetes.default.svc.cluster.local --service-account-key-file=/etc/kubernetes/pki/sa.pub --service-account-signing-key-file=/etc/kubernetes/pki/sa.key --service-cluster-ip-range=10.96.0.0/12 --tls-cert-file=/etc/kubernetes/pki/apiserver.crt --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
root 1485729 0.0 0.0 8172 2560 pts/0 S+ 18:22 0:00 grep --color=auto kube-apiserver
# Mirando los procesos vamos a ver el filesystem de este proceso
root@cks-master:~/kubernetes/server/bin# ls /proc/1479759/root
bin boot dev etc go-runner home lib proc root run sbin sys tmp usr var
# Buscando el kube-apiserver
root@cks-master:~/kubernetes/server/bin# find /proc/1479759/root/ | grep kube-apiserver
/proc/1479759/root/usr/local/bin/kube-apiserver
root@cks-master:~/kubernetes/server/bin# sha512sum /proc/1479759/root/usr/local/bin/kube-apiserver
e2a9bfddc7caa8139279b18b8c1588fc7f24fc87f44ec74a5878d90ab9c6d3003bdca81006058af0d189bd3ef2d99fc847e443a9242667129934f66960449ba3 /proc/1479759/root/usr/local/bin/kube-apiserver
# Obteniendo solo lo que necesitamos y haciendo append al archivo
root@cks-master:~/kubernetes/server/bin# sha512sum /proc/1479759/root/usr/local/bin/kube-apiserver | cut -d ' ' -f 1
e2a9bfddc7caa8139279b18b8c1588fc7f24fc87f44ec74a5878d90ab9c6d3003bdca81006058af0d189bd3ef2d99fc847e443a9242667129934f66960449ba3
root@cks-master:~/kubernetes/server/bin# sha512sum /proc/1479759/root/usr/local/bin/kube-apiserver | cut -d ' ' -f 1 >> apiserver-sha512
# Confirmando los valores internos y haciendo el uniq
root@cks-master:~/kubernetes/server/bin# cat apiserver-sha512
e2a9bfddc7caa8139279b18b8c1588fc7f24fc87f44ec74a5878d90ab9c6d3003bdca81006058af0d189bd3ef2d99fc847e443a9242667129934f66960449ba3
e2a9bfddc7caa8139279b18b8c1588fc7f24fc87f44ec74a5878d90ab9c6d3003bdca81006058af0d189bd3ef2d99fc847e443a9242667129934f66960449ba3
root@cks-master:~/kubernetes/server/bin# cat apiserver-sha512 | uniq
e2a9bfddc7caa8139279b18b8c1588fc7f24fc87f44ec74a5878d90ab9c6d3003bdca81006058af0d189bd3ef2d99fc847e443a9242667129934f66960449ba3
Ahora vamos a una curiosidad. Cuando ejecutamos el comando k get -n kube-system pod kube-apiserver-cks-master -o yaml podemos observar el siguiente bloque.
containerStatuses:
- containerID: containerd://84177449d2fcf44f6040a92914d5a5f9a2bb91c6bf341fb77026952b02092a30
image: registry.k8s.io/kube-apiserver:v1.30.3
# Tenemos este sha256
imageID: registry.k8s.io/kube-apiserver@sha256:a36d558835e48950f6d13b1edbe20605b8dfbc81e088f58221796631e107966c
Este sha256 es de la IMAGEN y no del binario. Vamos a verificar. Sabiendo que los archivos son los mismos, da igual si obtenemos el sha256 del binario del contenedor o del que descargamos. Si el archivo es exactamente el mismo y la función es la misma, entonces el hash es el mismo.
root@cks-master:~/kubernetes/server/bin# sha256sum kube-apiserver
286c16cf16389dfdfcd7e641859078c7b89f275c25e073d1c9f963cee393ccaa kube-apiserver
root@cks-master:~/kubernetes/server/bin# sha256sum /proc/1479759/root/usr/local/bin/kube-apiserver
286c16cf16389dfdfcd7e641859078c7b89f275c25e073d1c9f963cee393ccaa /proc/1479759/root/usr/local/bin/kube-apiserver
Y podemos ver que son dos hashes diferentes.
# Del binario
286c16cf16389dfdfcd7e641859078c7b89f275c25e073d1c9f963cee393ccaa
# De la Imagen
a36d558835e48950f6d13b1edbe20605b8dfbc81e088f58221796631e107966c
El kube-apiserver normalmente se construye sobre una imagen base extremadamente minimalista, generalmente basada en distribuciones Linux optimizadas para contenedores, como distroless o scratch.
Opciones Comunes:
-
Distroless: es un conjunto de imágenes base creadas por Google que contienen solo lo necesario para ejecutar la aplicación, sin shell, sin gestores de paquetes y sin ninguna herramienta que no sea esencial. Por qué usar: Estas imágenes son pequeñas, seguras y enfocadas en ejecutar la aplicación con el mínimo de sobrecarga.
-
Scratch: No es exactamente una imagen base, sino una base vacía, literalmente sin sistema operativo. Solo se incluyen binarios estáticos o extremadamente minimalistas. Por qué usar: Scratch es el nivel más alto de minimalismo posible para contenedores, y el kube-apiserver puede empaquetarse de esta forma si es completamente autosuficiente.
root@cks-master:~/kubernetes/server/bin# cat /proc/1479759/root/etc/os-release
PRETTY_NAME="Distroless"
NAME="Debian GNU/Linux"
ID="debian"
VERSION_ID="12"
VERSION="Debian GNU/Linux 12 (bookworm)"
HOME_URL="https://github.com/GoogleContainerTools/distroless"
SUPPORT_URL="https://github.com/GoogleContainerTools/distroless/blob/master/README.md"
BUG_REPORT_URL="https://github.com/GoogleContainerTools/distroless/issues/new"
Entonces queda ahí otro consejo de seguridad: reducir la superficie de ataque de las imágenes de tus microservicios usando imágenes base minimalistas.