Pular para o conteúdo principal

Image Security

Muitas imagens são pública não necessitando de uma autorização para que container runtime dentro de cada node faça o pull da imagem. Porém as imagens que rodam de fato as aplicações de uma organização não devem ser privadas e protegidas contra acesso não autorizado.

Vamos por exemplo olhar esse deployment ou se fosse pod tanto faz...

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

O container com o nome de nginx usará a imagem nginx mas de onde ela vem? Por default essas imagens referenciam o image registry do docker por isso podemos simplificar usando o nome nginx, pois o Kubernetes sabe que vem do docker.

Se fizéssemos isso daria no mesmo.

...
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 fazer um teste diferente para uma imagem em um repositório privado.

# Autenticando na minha conta 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

#baixando uma imagem 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

#criando uma nova tag, mas agora referenciando como se fosse minha
docker tag nginx:latest davidpuziol/nginx
docker push davidpuziol/nginx
Using default tag: latest
The push refers to repository [docker.io/davidpuziol/nginx] # <<< Olha para onde ela está indo
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 deslogar do docker e tentar fazer o pull
docker logout
Removing login credentials for https://index.docker.io/v1/

# E não podemos fazer o pull pois não temos permissão.
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

É exatamente isso que irá acontecer quando tentarmos criar uma deploy com essa imagem. Vamos testar.

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

Olhando o a imagem temos docker.io/davidpuziol/nginx que é a mesma coisa que docker.io/davidpuziol/nginx:latest

  • docker.io é o registry
  • davidpuziol é o useraccount
  • nginx é o nome da imagem
  • latest é a tag da imagem

Para fazer o processo de autenticação é necessário passar alguns parâmetros do login. Como fazer isso?

Vários outros registries existem além do docker.io. Temos: ECR da AWS, ACR da azure, GCR, ou até mesmo servers de repositório que nós criamos usando um Harbor que é muito utilizado no mundo open source.

O gcr possui muitas imagens públicas que são utilizados no cluster como por exemplo gcr.io/kubernetes-e2e-test-images/dnsutils

Dá uma olhada nesse registry que tem bastante coisa utilizada por ai além do docker.

Muitas imagens do Kubernetes que estavam nos repositórios do google (k8s.gcr.io) estão migrando para um outro repositório chamado 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

Bom, entendendo o processo de que precisamos estar logados, o runtime tentará fazer o login da mesma forma quando obter uma imagem privada.

Para passar as credenciais primeiro precisamos delas dentro de uma secret.

Se fossemos fazer uma secret que tivesse os dados do registry seria assim:

Crie um arquivo com esse conteúdo e faca o base64 dele que iremos precisar.

cat <<EOF | base64 -w 0
{
"auths": {
"docker.io": {
"username": "meuuser",
"password": "meupass123",
"email": "meu@email123"
}
}
}
EOF
ewogICJhdXRocyI6IHsKICAgICJkb2NrZXIuaW8iOiB7CiAgICAgICJ1c2VybmFtZSI6ICJtZXV1c2VyIiwKICAgICAgInBhc3N3b3JkIjogIm1ldXBhc3MxMjMiLAogICAgICAiZW1haWwiOiAibWV1QGVtYWlsMTIzIgogICAgfQogIH0KfQo=

Crie um manifesto passando o valor encodado. Observe que o tipo dessa secret é dockerconfigjson.

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Secret
metadata:
name: dockercreds
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: ewogICJhdXRocyI6IHsKICAgICJkb2NrZXIuaW8iOiB7CiAgICAgICJ1c2VybmFtZSI6ICJtZXV1c2VyIiwKICAgICAgInBhc3N3b3JkIjogIm1ldXBhc3MxMjMiLAogICAgICAiZW1haWwiOiAibWV1QGVtYWlsMTIzIgogICAgfQogIH0KfQo=
EOF

secret/dockercreds created

Agora usando podemos usar essa secret. Observe que isso é uma especificação do POD e não dos containers dentro do pod. Essa secret servirá para qualquer imagem dentro desse pod. Também podemos observar que é uma lista.

...
spec:
imagePullSecrets
- name: dockercreds
containers:
- name: nginx
image: docker.io/nginx
resources: {}
...

Pegando um exemplo que fiz com dados reais veja.

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

Geralmente não é muito bom criar secrets em manifestos logo podemos fazer o 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"}}}%