# Harbour como Registro de Imágenes y Kyverno para forzar políticas

Hace unos meses nos preguntamos: ¿Sabemos qué imágenes usamos en nuestros entornos? ¿Son las que fueron aprobadas por Seguridad? ¿Podría un atacante desplegar imágenes adulteradas? Hicimos esta [entrada](https://blog.santiagoagustinfernandez.com/firma-verificacion-de-images-con-kyverno-cosign) en el blog donde creamos políticas en nuestro clúster Kubernetes con **Kyverno** para permitir solo el despliegue de imágenes firmadas y aprobadas por Seguridad. No es nada nuevo decir que la seguridad es como una capa de cebolla, así que me pregunté: ¿Y si alojamos el Registro de Imágenes? ¡Manos a la obra!

Para ello, vamos a usar Harbour, un registro open source que ofrece una variedad de características, como escaneo, firma, autenticación, auditoría, etc. Además, acercamos estas imágenes a nuestro entorno en tiempo de ejecución.

## Instalación de Harbor

Primero deberíamos tener nginx ingress para poder consumir la instalación de Harbour. Vamos a pegar una mirada a nuestro cluster.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721506644269/92b8a9fd-f8cd-4070-94e3-b4ab081e9c66.png align="center")

Vamos agregar los helm's necesarios y editar alguna linea, para configurar la interfaz a consumir.

```yaml
helm repo add harbor https://helm.goharbor.io
```

Vamos a descargar el helm para editar esos archivos.

```yaml
helm fetch harbor/harbor --untar
```

Deberíamos poder abrir un editor y ver estos archivos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721506964489/c32de66e-38e2-4fd8-af61-798fd9ffe7d1.png align="center")

Edito el FQDN que voy a utilizar, de manera local.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721659550665/fa74d0f8-c99d-4456-8144-c17a6d92f0ae.png align="center")

y

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721691497915/930526c7-5606-4ffb-9463-c20b4f317dfe.png align="center")

Ahora si instalamos.

```yaml
helm install harbor ./harbor -n harbor --create-namespace
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721507426558/b699504d-04de-4cd7-b855-769e427c4d1e.png align="center")

Vamos a ver el ingress y apunto la resolucion local al Cluster de Kubernetes.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721659534535/030f9020-19eb-471c-a760-fc95a78a3293.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721659644075/7477e5a1-1e8c-442b-bb34-fe1bd571e3aa.png align="center")

Vamos a probar el ingreso, con las credenciales por defecto.

```yaml
Username: admin
Password: Harbor12345
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721659707751/39bf9826-780d-4884-849e-ad3af59860a5.png align="center")

¡Perfecto! Ya tenemos nuestro servidor de registro listo.

### Creación de Proyecto

Vamos a crear un proyecto, llamado blog.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721659909923/5163bd8c-2e1a-4a30-b1a5-920ea33fd0bd.png align="center")

### Push Imagen Local

Ahora que tenemos nuestro registro de imágenes, vamos a subir una imagen a Harbor. Para ello, necesitamos autenticarnos.

En nuestro caso, como tenemos un certificado no válido, vamos a configurar el Engine de Docker para aceptar un registro no estándar. Esto no es seguro, pero para nuestra prueba de concepto, es suficiente.

Agregamos la sentencia

```yaml
  "insecure-registries": [
    "harbor.esprueba.com"
  ]
```

Y reiniciamos Docker.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721691638241/d870a8ed-dbbb-443a-975a-71ce7a5c5625.png align="center")

Ahora si podemos autenticarnos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721691526396/51005a55-bf6e-40c1-9560-25f00d84ae38.png align="center")

Vamos a descargar la imagen de Nginx, luego la "etiquetamos" y la subimos a Harbor.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721692454737/73d50c7d-52a3-438e-a021-5b00bc4a5106.png align="center")

Listo, ya está en el servidor de imagenes.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721747054448/eda667a2-8406-484a-87e8-0ec58d1d3fcf.png align="center")

## Policy as a Code

Podemos verla en la entrada que les comente al principio, si no estaremos el paso a paso aca. Una vez instalado vamos a crear una política que solo acepte imágenes de nuestra instancia de Harbor en un Namespace específico.

### Instalación Kyverno

Vamos a agregar el repositorio de helm para luego instalarlo.

```yaml
helm repo add kyverno-repo https://kyverno.github.io/kyverno/
helm install kyverno kyverno-repo/kyverno -n kyverno-ns --create-namespace
```

¡Listo! Ya tenemos Kyverno en el clúster con todos sus componentes.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721747589983/7a2b39a0-4b04-4de3-af4f-d2aa4a65ffe3.png align="center")

### Creación Política

Vamos a crear la política que comentamos anteriormente, para que solo acepte imagenes de Harbor y se pueda deployar en el namespace nginx-app.

```yaml
apiVersion : kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: check-images
spec:
  validationFailureAction: Enforce
  background: false
  rules:
  - name: check-registry
    match:
      any:
      - resources:
          kinds:
          - Pod
          namespaces:
          - nginx-app
      
    preconditions:
      any:
      - key: "{{request.operation}}"
        operator: NotEquals
        value: DELETE
    validate:
      message: "unknown registry"
      foreach:
      - list: "request.object.spec.initContainers"
        pattern:
          image: "harbor.esprueba.com/*"
      - list: "request.object.spec.containers"
        pattern:
          image: "harbor.esprueba.com/*"
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721747915554/268b3f02-0fb3-4e03-b0c0-67ce412a16a7.png align="center")

Vamos a crear un deployment para la prueba de concepto, usando una imagen de DockerHub. Si todo ha salido bien, no debería poder hacer el deployment.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: nginx-app
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721748127761/12845986-cde4-459e-9e97-6f05f19abe06.png align="center")

Excelente! No se está pudiendo crear el contenedor porque Kyverno lo esta parando.

Ahora si, vamos a ver si el registro es Harbor que pasa.

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: nginx-app
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: harbor.esprueba.com/blog/nginx:alpine
        ports:
        - containerPort: 80
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1721749281417/b174caf6-9e1b-4e13-a08b-878c4339d9a2.png align="center")

Listo se estan creando los contenedores. La política de Kyveno que creamos permite que las imagenes de harbor.esprueba.com/blog/nginx:alpine se desplieguen en el namespace nginx-app.

Espero que les sirva, nos vemos en la próxima entrada.
