Image Security
Muchas imágenes son públicas no necesitando una autorización para que container runtime dentro de cada node haga el pull de la imagen. Pero las imágenes que ejecutan de hecho las aplicaciones de una organización deben ser privadas y protegidas contra acceso no autorizado.
Vamos por ejemplo a mirar este deployment o si fuera pod da igual...
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
app: nginx
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
strategy: {}
template:
metadata:
creationTimestamp: null
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
resources: {}
status: {}
El container con el nombre de nginx usará la imagen nginx pero ¿de dónde viene? Por defecto estas imágenes referencian el image registry de docker por eso podemos simplificar usando el nombre nginx, pues Kubernetes sabe que viene de docker.
Si hiciéramos esto daría lo mismo.
...
spec:
containers:
- name: nginx
image: docker.io/nginx
resources: {}
...
kubectl create deployment nginx --image docker.io/nginx
deployment.apps/nginx created
➜ files git:(main) ✗ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 18s
Vamos a hacer una prueba diferente para una imagen en un repositorio privado.
# Autenticando en mi cuenta docker
docker login
Authenticating with existing credentials...
WARNING! Your password will be stored unencrypted in /home/david-prata/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
# descargando una imagen pública
docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
c57ee5000d61: Pull complete
9b0163235c08: Pull complete
f24a6f652778: Pull complete
9f3589a5fc50: Pull complete
f0bd99a47d4a: Pull complete
398157bc5c51: Pull complete
1ef1c1a36ec2: Pull complete
Digest: sha256:84c52dfd55c467e12ef85cad6a252c0990564f03c4850799bf41dd738738691f
Status: Downloaded newer image for nginx:latest
docker.io/library/nginx:latest
# creando una nueva tag, pero ahora referenciando como si fuera mía
docker tag nginx:latest davidpuziol/nginx
docker push davidpuziol/nginx
Using default tag: latest
The push refers to repository [docker.io/davidpuziol/nginx] # <<< Mira hacia dónde está yendo
f205d290cd76: Mounted from library/nginx
2b28485849ea: Mounted from library/nginx
9f21a390e3f6: Mounted from library/nginx
06536efc503a: Mounted from library/nginx
84e0c9ef07d7: Mounted from library/nginx
83bdf27d9eaa: Mounted from library/nginx
fb1bd2fc5282: Mounted from library/nginx
latest: digest: sha256:6a9af2366105c104e353d16998458d6a15aa5d6db0861ad9ce98538890391950 size: 1778
# Vamos a desloguearnos de docker e intentar hacer el pull
docker logout
Removing login credentials for https://index.docker.io/v1/
# Y no podemos hacer el pull pues no tenemos permiso.
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
Es exactamente esto lo que ocurrirá cuando intentemos crear un deploy con esta imagen. Vamos a probar.
kubectl create deployment nginx-david --image docker.io/davidpuziol/nginx
deployment.apps/nginx-david created
kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 7m1s
nginx-david 0/1 1 0 11s
kubectl get pods nginx-david-54ccffddf9-mm5jp
NAME READY STATUS RESTARTS AGE
nginx-david-54ccffddf9-mm5jp 0/1 ImagePullBackOff 0 24s
Mirando la imagen tenemos docker.io/davidpuziol/nginx que es lo mismo que docker.io/davidpuziol/nginx:latest
- docker.io es el registry
- davidpuziol es el useraccount
- nginx es el nombre de la imagen
- latest es el tag de la imagen
Para hacer el proceso de autenticación es necesario pasar algunos parámetros del login. ¿Cómo hacer esto?
Varios otros registries existen además de docker.io. Tenemos: ECR de AWS, ACR de azure, GCR, o hasta servidores de repositorio que nosotros creamos usando un Harbor que es muy utilizado en el mundo open source.
El gcr posee muchas imágenes públicas que son utilizadas en el cluster como por ejemplo gcr.io/kubernetes-e2e-test-images/dnsutils
Dale un vistazo a este registry que tiene bastante cosa utilizada por ahí además de docker.
Muchas imágenes de Kubernetes que estaban en los repositorios de google (k8s.gcr.io) están migrando a otro repositorio llamado registry.k8s.io
kubectl describe pods -n kube-system kube-apiserver-kind-cluster-control-plane | grep Image:
Image: registry.k8s.io/kube-apiserver:v1.29.1
Bien, entendiendo el proceso de que necesitamos estar logueados, el runtime intentará hacer el login de la misma forma cuando obtenga una imagen privada.
Para pasar las credenciales primero necesitamos de ellas dentro de una secret.
Si fuéramos a hacer una secret que tuviera los datos del registry sería así:
Crea un archivo con este contenido y haz el base64 de él que vamos a necesitar.
cat <<EOF | base64 -w 0
{
"auths": {
"docker.io": {
"username": "meuuser",
"password": "meupass123",
"email": "meu@email123"
}
}
}
EOF
ewogICJhdXRocyI6IHsKICAgICJkb2NrZXIuaW8iOiB7CiAgICAgICJ1c2VybmFtZSI6ICJtZXV1c2VyIiwKICAgICAgInBhc3N3b3JkIjogIm1ldXBhc3MxMjMiLAogICAgICAiZW1haWwiOiAibWV1QGVtYWlsMTIzIgogICAgfQogIH0KfQo=
Crea un manifesto pasando el valor encodado. Observa que el tipo de esta secret es dockerconfigjson.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: dockercreds
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: ewogICJhdXRocyI6IHsKICAgICJkb2NrZXIuaW8iOiB7CiAgICAgICJ1c2VybmFtZSI6ICJtZXV1c2VyIiwKICAgICAgInBhc3N3b3JkIjogIm1ldXBhc3MxMjMiLAogICAgICAiZW1haWwiOiAibWV1QGVtYWlsMTIzIgogICAgfQogIH0KfQo=
EOF
secret/dockercreds created
Ahora usando podemos usar esta secret. Observa que esto es una especificación del POD y no de los containers dentro del pod. Esta secret servirá para cualquier imagen dentro de este pod. También podemos observar que es una lista.
...
spec:
imagePullSecrets
- name: dockercreds
containers:
- name: nginx
image: docker.io/nginx
resources: {}
...
Tomando un ejemplo que hice con datos reales mira.
kubectl get deployments.apps nginx-david -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
...
generation: 1
labels:
app: nginx-david
spec:
...
selector:
matchLabels:
app: nginx-david
...
template:
metadata:
creationTimestamp: null
labels:
app: nginx-david
spec:
containers:
- image: docker.io/davidpuziol/nginx:latest
imagePullPolicy: Always
name: nginx
resources: {}
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
dnsPolicy: ClusterFirst
imagePullSecrets: #<<< Usada
- name: docker-david-secret
restartPolicy: Always
schedulerName: default-scheduler
securityContext: {}
terminationGracePeriodSeconds: 30
...
kubectl get pods nginx-david-c8644f94d-nlgch
NAME READY STATUS RESTARTS AGE
nginx-david-c8644f94d-nlgch 1/1 Running 0 15m
kubectl describe pods nginx-david-c8644f94d-nlgch| grep Image:
Image: docker.io/davidpuziol/nginx:latest
Generalmente no es muy bueno crear secrets en manifestos luego podemos hacer el comando.
kubectl create secret docker-registry dockerreq --docker-email [email protected] --docker-username meutest --docker-password pass123 --docker-server docker.io
secret/dockerreq created
kubectl get secrets dockerreq -o yaml
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJkb2NrZXIuaW8iOnsidXNlcm5hbWUiOiJtZXV0ZXN0IiwicGFzc3dvcmQiOiJwYXNzMTIzIiwiZW1haWwiOiJtZXVAdGVzdC5jb20iLCJhdXRoIjoiYldWMWRHVnpkRHB3WVhOek1USXoifX19
kind: Secret
metadata:
creationTimestamp: "2024-02-12T20:41:04Z"
name: dockerreq
namespace: default
resourceVersion: "479885"
uid: 72a67fab-256e-414c-94c7-bb76badd19a0
type: kubernetes.io/dockerconfigjson
echo eyJhdXRocyI6eyJkb2NrZXIuaW8iOnsidXNlcm5hbWUiOiJtZXV0ZXN0IiwicGFzc3dvcmQiOiJwYXNzMTIzIiwiZW1haWwiOiJtZXVAdGVzdC5jb20iLCJhdXRoIjoiYldWMWRHVnpkRHB3WVhOek1USXoifX19 | base64 --decode
{"auths":{"docker.io":{"username":"meutest","password":"pass123","email":"[email protected]","auth":"bWV1dGVzdDpwYXNzMTIz"}}}%