Registros de Contenedores
Un software es como cualquier otro producto que sigue un flujo de la producción hasta el usuario final
- Material (tools)
- Fabricación (Desarrollo)
- Montaje (Build, en caso de contenedores creación de la imagen)
- Distribución (CI|CD para el registry)
- Implantación (Deploy)
- Consumo (Browser)
Necesitamos tener certeza de que el producto fabricado y distribuido es de hecho lo que será consumido por el usuario final.
Durante el proceso de pull de una imagen distribuida en un repositorio, este repositorio puede ser público o privado. Si es público, para hacer el pull no es necesario ningún tipo de autenticación, la imagen está allí disponible para cualquiera usar. Si es privado es necesario utilizar las credenciales correctas para que el pull sea permitido. Obviamente los contenedores con softwares de negocio de la empresa no serán públicos.
Si hacemos un docker pull para un registry privado tendremos un acceso denegado.
haciendo el comando docker login vamos a hacer el acceso con un usuario específico y caso este usuario tenga permiso para hacer el pull de determinada imagen será posible.
❯ docker pull davidpuziol/nginx
Using default tag: latest
Error response from daemon: pull access denied for davidpuziol/nginx, repository does not exist or may require 'docker login': denied: requested access to the resource is denied
❯ docker login
Log in with your Docker ID or email address to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com/ to create one.
You can log in with your password or a Personal Access Token (PAT). Using a limited-scope PAT grants better security and is required for organizations using SSO. Learn more at https://docs.docker.com/go/access-tokens/
Username: davidpuziol
Password:
WARNING! Your password will be stored unencrypted in /home/david/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
Login Succeeded
❯ docker pull davidpuziol/nginx
Using default tag: latest
latest: Pulling from davidpuziol/nginx
c57ee5000d61: Extracting [==================================================>] 29.15MB/29.15MB
9b0163235c08: Download complete
f24a6f652778: Download complete
9f3589a5fc50: Download complete
f0bd99a47d4a: Waiting
398157bc5c51: Waiting
1ef1c1a36ec2: Waiting
context canceled
Cuando utilizamos una imagen privada en Kubernetes es necesario pasar las credenciales y ¿cómo haremos esto? Creando un secret con tipo docker-registry con los valores abajo.
Vamos a hacer un filtro en el clúster para listar todas las imágenes que tenemos usadas por los pods.
# La salida suele ser todo en una línea, sustituimos los espacios por \n para saltar de línea y como tenemos más de un pod usando la misma imagen podemos usar uniq para no traer salidas repetidas.
docker.io/kubernetesui/dashboard-web:1.4.0root@cks-master:~# k get pod -A -o jsonpath="{..spec.containers[*].image}" | tr ' ' '\n' | uniq
nginx
openpolicyagent/gatekeeper:v3.17.0
registry.k8s.io/ingress-nginx/controller:v1.11.2@sha256:d5f8217feeac4887cb1ed21f27c2674e58be06bd8f5184cacea2a69abaf78dce
docker.io/calico/kube-controllers:v3.24.1
docker.io/calico/node:v3.24.1
quay.io/coreos/flannel:v0.15.1
docker.io/calico/node:v3.24.1
quay.io/coreos/flannel:v0.15.1
registry.k8s.io/coredns/coredns:v1.11.1
registry.k8s.io/etcd:3.5.15-0
registry.k8s.io/kube-apiserver:v1.31.0
registry.k8s.io/kube-controller-manager:v1.31.0
registry.k8s.io/kube-proxy:v1.31.0
registry.k8s.io/kube-scheduler:v1.31.0
docker.io/kubernetesui/dashboard-api:1.7.0
docker.io/kubernetesui/dashboard-auth:1.1.3
kong:3.6
docker.io/kubernetesui/dashboard-metrics-scraper:1.1.1
docker.io/kubernetesui/dashboard-web:1.4.0
Tenemos el siguiente patrón <REGISTRY>/<ACCOUNT>/<IMAGE_NAME>:<TAG>
Podemos observar que en ese patrón tenemos los registries docker.io, registry.k8s.io, quay.io.
Cuando encontramos el patrón <ACCOUNT>/<IMAGE_NAME>:<TAG> queda sobreentendido que también es docker.io como por ejemplo openpolicyagent/gatekeeper:v3.17.0.
Cuando tenemos el patrón <IMAGE_NAME>:<TAG> esto quiere decir que esta imagen es oficial en dockerhub. Esto quiere decir que está en el namespace library o sea, queda sobreentendido que es docker.io/library. Vamos a tomar nginx como ejemplo. Si fuera nginx/nginx esto indicaría que la imagen nginx pertenecería a la organización nginx lo que no es el caso. Aunque el equipo de nginx mantenga esta imagen ella tiene un lugar especial.
# Vamos a hacer el pull usando el path completo de nginx
root@cks-master:~# docker pull docker.io/library/nginx
Using default tag: latest
latest: Pulling from library/nginx
a2318d6c47ec: Pull complete
095d327c79ae: Pull complete
bbfaa25db775: Pull complete
7bb6fb0cfb2b: Pull complete
0723edc10c17: Pull complete
24b3fdc4d1e3: Pull complete
3122471704d5: Pull complete
Digest: sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
# Observa que ya tenía todas las capas y ni necesitar hacer más pulling y el digest es exactamente el mismo.
root@cks-master:~# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
Digest: sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
Status: Image is up to date for nginx:latest
docker.io/library/nginx:latest
Vamos a hacer un análisis de nginx por ejemplo, pero podría ser de cualquier otro pod. Voy a poner la salida completa. Voy a remover algunas partes y dejar solamente lo que interesa.
root@cks-master:~# k get pod nginx -o yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
#{
# Bloques removidos para facilitar la lectura
#}
labels:
run: nginx
name: nginx
namespace: default
#{
# Bloques removidos para facilitar la lectura
#}
spec:
automountServiceAccountToken: false
containers:
- image: nginx # Aquí tenemos la imagen de nginx, pero en status será convertida
imagePullPolicy: Always
name: nginx
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 250m
memory: 128Mi
...
status:
conditions:
- lastProbeTime: null
lastTransitionTime: "2024-09-02T20:26:44Z"
status: "True"
type: PodReadyToStartContainers
#{
# Bloques removidos para facilitar la lectura
#}
containerStatuses:
- containerID: containerd://bbb1f84d332f3726b46040d020764d29acb7aa3c4538d1b6fad5eadcd4fe9162
image: docker.io/library/nginx:latest # <<<<< camino completo de ella
# Y fue creado un imageID convirtiendo la tag latest para el digest que referencia exactamente la versión que está siendo usada.
imageID: docker.io/library/nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
lastState: {}
name: nginx
ready: true
restartCount: 0
started: true
state:
running:
startedAt: "2024-09-02T20:26:43Z"
#{
# Bloques removidos para facilitar la lectura
#}
root@cks-master:~#
Es necesario estar atento siempre a las tags y fijar la versión.
Podemos hacer el pull usando el digest. Ya tenemos la imagen, esto es solo para mostrar cómo funciona.
root@cks-master:~# docker pull docker.io/library/nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
docker.io/library/nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533: Pulling from library/nginx
Digest: sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
Status: Image is up to date for nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
docker.io/library/nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
Podemos recrear el pod usando exactamente así...
root@cks-master:~# k run nginx2 --image=docker.io/library/nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
root@cks-master:~# k get pod nginx2 -o yaml
apiVersion: v1
kind: Pod
metadata:
#{
# Bloques removidos para facilitar la lectura
#}
creationTimestamp: "2024-09-05T16:34:18Z"
labels:
run: nginx2
name: nginx2
namespace: default
spec:
containers:
- image: docker.io/library/nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
imagePullPolicy: IfNotPresent
name: nginx2
resources: {}
#{
# Bloques removidos para facilitar la lectura
#}
status:
conditions:
#{
# Bloques removidos para facilitar la lectura
#}
containerStatuses:
- containerID: containerd://8e26697bfe9ca178e17aedc7cdada3bf09c8cf44e78ccbf05a00def5087f1521
image: docker.io/library/nginx:latest # Y fue hecho el proceso inverso
imageID: docker.io/library/nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
#{
# Bloques removidos para facilitar la lectura
#}
Whitelist Images
Para prevenir que imágenes sean buscadas de otros repositorios podemos hacer una restricción.
Para esta tarea es necesario OPA instalado.
Vamos a hacer una restricción para docker.io y registry.k8s.io. Primero creamos la plantilla y después la constraint.
root@cks-master:~# vim constraint-reg.yaml
root@cks-master:~# cat constraint-reg.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8strustedimages
spec:
crd:
spec:
names:
kind: K8sTrustedImages
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8strustedimages
violation[{"msg": msg}] {
image := input.review.object.spec.containers[_].image
not startswith(image, "docker.io/")
not startswith(image, "registry.k8s.io/")
msg := "not trusted image!"
}
root@cks-master:~# vim constraint-rule.yaml
root@cks-master:~# cat constraint-rule.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sTrustedImages
metadata:
name: pod-trusted-images
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
root@cks-master:~#
root@cks-master:~# k apply -f constraint-reg.yaml
constrainttemplate.templates.gatekeeper.sh/k8strustedimages created
root@cks-master:~# k apply -f constraint-rule.yaml
k8strustedimages.constraints.gatekeeper.sh/pod-trusted-images created
root@cks-master:~# k get constrainttemplate
NAME AGE
k8strustedimages 87s
root@cks-master:~# k get constraint pod-trusted-images
NAME ENFORCEMENT-ACTION TOTAL-VIOLATIONS
pod-trusted-images deny 8
La violación ocurrirá si no inicia con docker.io o registry.k8s.io haciendo que la petición sea denegada valiendo a partir del momento de la creación de la constraint.
Podemos observar que tenemos 8 violaciones, ya que por ejemplo la imagen solo con el nombre nginx no sería aceptada pero docker.io/library/nginx sería.
En la propia regla que creamos la imagen de OPA gatekeeper también sufre la violación.
Probando...
root@cks-master:~# k run httpd --image=httpd
Error from server (Forbidden): admission webhook "validation.gatekeeper.sh" denied the request: [pod-trusted-images] not trusted image!
root@cks-master:~# k run httpd --image=docker.io/library/httpd
pod/httpd created
ImagePolicyWebhook
El flujo estándar es este. Si activamos ImagePolicyWebhook entonces la petición también será filtrada por este plugin además de otros que ya existen.

ImagePolicyWebhook llamará a un servicio externo, fuera del control de kubernetes. Es necesario desarrollar la solución y tenerla en funcionamiento para recibir un objeto del tipo ImageReview y este servicio aprobará o denegará.
El objeto es algo como este.

La solución puede ser desarrollada en cualquier lenguaje, no importa, lo importante es seguir las definiciones de los objetos. El flujo entonces se convierte en esto.

Vamos a simular esta idea, pero antes elimina las constraints creados con OPA para no dar problema.
es necesario activar en kube-apiserver el plugin.
# solo tenemos habilitado NodeRestriction entonces edita y pon
# - --enable-admission-plugins=NodeRestriction
root@cks-master:~# k get -n kube-system pod kube-apiserver-cks-master -o yaml| grep admission
- --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook
root@cks-master:~#