Pular para o conteúdo principal

Container Registries

Um software é como qualquer outro produto que segue um fluxo da produção até o usuário final

  1. Material (tools)
  2. Fabricação (Desenvolvimento)
  3. Montagem (Build, no caso de contêineres criação da imagem)
  4. Distribuição (CI|CD para o registry)
  5. Implantação (Deploy)
  6. Consumo (Browser)

Precisamos ter certeza de que o produto fabricado e distribuído é de fato o que será consumido pelo usuário final.

Durante o processo de pull de uma imagem distribuída em um repositório, este repositório pode ser público ou privado. Se for público, para fazer o pull não é necessário nenhum tipo de autenticação, a imagem está lá disponível para qualquer um usar. Se for privado é necessário utilizar as credenciais corretas para que o pull seja permitido. Obviamente os contêineres com softwares de negócio da empresa não serão públicos.

Se fizermos um docker pull para um registry privado teremos um acesso negado.

fazendo o comando docker login vamos fazer o acesso com um usuário específico e caso este usuário tenha permissão para fazer o pull de determinada imagem será possível.

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

Quando utilizamos uma imagem privada no Kubernetes é necessário passar as credenciais e como faremos isso? Criando uma secret com tipo docker-registry com os valores abaixo.

Vamos fazer um filtro no cluster para listar todas as imagens que temos usadas pelos pods.

# A saída costuma ser tudo em uma linha, substituímos os espaços por \n para pular de linha e como temos mais de um pod usando a mesma imagem podemos usar o uniq para não trazer saídas 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

Temos o seguinte padrão <REGISTRY>/<ACCOUNT>/<IMAGE_NAME>:<TAG> Podemos observar que nesse padrão temos os registries docker.io, registry.k8s.io, quay.io.

Quando encontramos o padrão <ACCOUNT>/<IMAGE_NAME>:<TAG> fica subtendido que também é o docker.io como por exemplo openpolicyagent/gatekeeper:v3.17.0.

Quando temos o padrão <IMAGE_NAME>:<TAG> isso quer dizer que esta imagem é oficial no dockerhub. Isso quer dizer que esta no namespace library ou seja, fica subtendido que é docker.io/library. Vamos tomar o nginx como exemplo. Se fosse nginx/nginx isso indicaria que a imagem nginx pertenceria a organização nginx o que não é o caso. Apesar da equipe do nginx manter esta imagem ela tem um lugar especial.

# Vamos faz o pull usando o path completo do 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

# Observe que ele já tinha todas as layers e nem precisar fazer mais pulling e o digest é exatamente o mesmo.
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 fazer uma análise do nginx por exemplo, mas poderia ser de qualquer outro pod. Vou colocar a saída completa. Vou remover algumas partes e deixar somente o que interessa.

root@cks-master:~# k get pod nginx -o yaml
apiVersion: v1
kind: Pod
metadata:
annotations:
#{
# Blocos removidos para facilitar a leitura
#}
labels:
run: nginx
name: nginx
namespace: default
#{
# Blocos removidos para facilitar a leitura
#}
spec:
automountServiceAccountToken: false
containers:
- image: nginx # Aqui temos a imagem do nginx, mas em status ela 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
#{
# Blocos removidos para facilitar a leitura
#}
containerStatuses:
- containerID: containerd://bbb1f84d332f3726b46040d020764d29acb7aa3c4538d1b6fad5eadcd4fe9162
image: docker.io/library/nginx:latest # <<<<< caminho completo dela
# E foi criado uma o imageID convertendo a tag latest para o digest que referencia exatamente a versão que está sendo 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"
#{
# Blocos removidos para facilitar a leitura
#}
root@cks-master:~#

É necessário ficar atento sempre as tags e fixar a versão.

Podemos fazer o pull usando o digest. Já temos a imagem, isso é só para mostrar como 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 recriar o pod usando exatamente assim...

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:
#{
# Blocos removidos para facilitar a leitura
#}
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: {}
#{
# Blocos removidos para facilitar a leitura
#}
status:
conditions:
#{
# Blocos removidos para facilitar a leitura
#}
containerStatuses:
- containerID: containerd://8e26697bfe9ca178e17aedc7cdada3bf09c8cf44e78ccbf05a00def5087f1521
image: docker.io/library/nginx:latest # E foi feito o processo inverso
imageID: docker.io/library/nginx@sha256:01b9ff8541cd3f5503a081844cbcaa1faac6a74fb41328061ea1917d0b513533
#{
# Blocos removidos para facilitar a leitura
#}

Whitelist Images

Para prevenir que imagens sejam buscadas de outros repositórios podemos fazer uma restrição.

Para essa tarefa é necessário o OPA instalado.

Vamos fazer uma restrição para docker.io e registry.k8s.io. Primeiro criamos o template e depois a 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(/docs/kubernetes/certifications/cks/suply-chain/pics/image, "docker.io/")
not startswith(/docs/kubernetes/certifications/cks/suply-chain/pics/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

A violação acontecerá se não iniciar com docker.io ou registry.k8s.io fazendo a request ser negada valendo a partir do momento da criação da constraint.

Podemos observar que temos 8 violações, pois por exemplo a imagem só com o nome nginx não iria ser aceita mas docker.io/library/nginx seria.

Na prória regra que criamos a imagem do OPA gatekeeper também sofre a violação.

Testando...

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

O fluxo padrão é esse. Se ativarmos o ImagePolicyWebhook então a request também será filtrada por esse plugin além de outros que já existem.

alt text

ImagePolicyWebhook chamará um um serviço externo, fora do controle do kubernetes. É necessário desenvolver a solução e tê-la em funcionamento para receber um objeto do tipo ImageReview e este serviço irá aprovar ou negar.

O objeto é algo como este.

alt text

A solução pode ser desenvolvida em qualquer linguagem, não importa, o importante é seguir a definições dos objetos. O fluxo então se torna assim.

alt text

Vamos simular essa idéia, mas antes delete as constrainsts criados com o OPA para não dar problema.

é necessário ativar no kube-apiserver o plugin.

# só temos habilitado o NodeRestriction então edite e coloque
# - --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:~#

Todo