# Superpipeline DevSecOps con IA

En el mundo del desarrollo moderno, **la seguridad no puede ser una idea tardía**. Los equipos enfrentan el desafío constante de mantener la velocidad de desarrollo sin comprometer la seguridad de sus aplicaciones. Este artículo documenta la construcción de un pipeline DevSecOps completo que integra seguridad automatizada, análisis con IA y visibilidad centralizada.

### El Desafío

Como profesional de ciberseguridad, me enfrenté a varios problemas comunes en equipos de desarrollo:

1. **Secretos hardcodeados** que terminan en producción
    
2. **Vulnerabilidades en dependencias** que pasan desapercibidas
    
3. **Código inseguro** que se detecta demasiado tarde
    
4. **Falta de visibilidad** sobre el estado de seguridad real
    
5. **Errores de pipeline** sin contexto ni soluciones claras
    

La pregunta era: **¿Cómo podemos hacer que la seguridad sea automática, informativa y no obstructiva** La Solución: Un Pipeline DevSecOps con IA

Construí un pipeline completo que combina:

* **GitLab CI/CD** para orquestación
    
* **4 herramientas de seguridad** (Bandit, Safety, GitLeaks, Trivy)
    
* **DefectDojo** para gestión de vulnerabilidades
    
* **n8n** para automatización inteligente
    
* **Ollama (Mistral 7B)** para análisis de errores con IA
    
* **Slack** para notificaciones contextuales
    
* **Kubernetes (k3s)** para deployment
    
* **Cloudflare Tunnel** para exposición segura
    

Vamos a descargar el repositorio que tengo los archivos ahi. Primero echamos a correr nuestro docker compose, para levantar el stack.

```bash
❯ sudo git clone https://github.com/safernandez666/devsecopsia.git
Clonando en 'devsecopsia'...
remote: Enumerating objects: 29, done.
remote: Counting objects: 100% (29/29), done.
remote: Compressing objects: 100% (23/23), done.
remote: Total 29 (delta 6), reused 27 (delta 4), pack-reused 0 (from 0)
Recibiendo objetos: 100% (29/29), 16.45 KiB | 1.10 MiB/s, listo.
Resolviendo deltas: 100% (6/6), listo.
```

Vamos a echarlo a correr.

```bash
docker compose up
```

### Kubernetes

Como pueden ver, tenemos **k3s** como nodo de Kubernetes. Se generará una carpeta llamada k3s-kubeconfig. Lo primero que haremos es configurarla para poder gestionarla mediante **kubectl**.

```bash
export KUBECONFIG=/Users/santiago/Proyects/devsecopsia/k3s-kubeconfig/kubeconfig.yaml
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763153660549/a0dbb4b4-d172-417c-a5b9-93b5b64a794c.png align="center")

Nuestro clúster está en funcionamiento. Vamos a desplegar manualmente nuestra aplicación usando `kubectl apply -f k8s/`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763160975430/97e538cd-a26f-4cab-8ff8-fa1cff88a43f.png align="center")

¡Listo! Nuestra aplicación está funcionando.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763161062609/864d2563-0645-48d5-b822-f9623a4911c6.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Es importante que crees tu tunnel de Cloudflare con tus datos. Este es un dominio que uso de prueba, para mis PoC.</div>
</div>

Sigamos con los demás componentes.

## Gitlab

Vamos a iniciar sesión en http://localhost:8000 con el usuario: `root` y la contraseña: `changeme123`.

### Outbound

Una vez dentro, configuraremos algunos elementos específicos, como el **Outbound** para habilitar las integraciones.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1762461311420/90e619cf-51e5-4128-9adf-d8d21ee02923.png align="center")

### Proyecto

Ahora vamos a crear el proyecto y subir el código.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763154158216/a3b90c76-abb7-4273-8014-910a4a280a67.png align="center")

```bash
# Eliminamos .git
sudo rm -R .git

git init --initial-branch=master
git remote add origin http://localhost:8000/root/python-application.git
git add .
git commit -m "Initial commit"
git push --set-upstream origin master
```

Listo! Tenemos nuestro proyecto en Gitlab.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763154429909/91dbee32-6d29-4ccc-9853-1d7ca4f586c9.png align="center")

### Webook

Una vez generado el proyecto vamos a crear el **Webhook** que hablara con **N8N** cada vez que hay un evento en el pipeline. Tenemos que tildar `Pipeline events` para la PoC vamos a deshabilitar el SSL y la ruta sera del Webhook sera `http://n8n:5678/webhook/gitlab-ai`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763155760492/d354aca0-9f3e-49f4-8cfc-b79c59cf3473.png align="center")

### **Registrar Runner**

Vamos a registrar el runner que se encargará de ejecutar nuestro proyecto. Para ello nos vamos a meter dentro del docker gitlab-runner y ejecutar algunos comandos. Previo a eso necesitamos un token. Ingresamos al docker con:

```bash
docker exec -it gitlab-runner sh
```

Ahí aplicamos el comando que nos da Gitlab.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763155054365/0462ab15-bf93-4688-9aaf-66f96853e6fd.png align="center")

Vamos a editar el config.toml de runner.

```bash
docker exec -it gitlab-runner vi /etc/gitlab-runner/config.toml
```

Deberia quedar asi:

```bash
concurrent = 1
check_interval = 0
shutdown_timeout = 0

[session_server]
  session_timeout = 1800

[[runners]]
  name = "docker-desktop"
  url = "http://localhost:8000"
  id = 1
  token = "glrt-GWHqJu72JQo3xR1-1okEwW86MQp0OjEKdToxCw.01.1215lwx2d"
  token_obtained_at = 2025-11-14T21:15:44Z
  token_expires_at = 0001-01-01T00:00:00Z
  executor = "docker"
  [runners.cache]
    MaxUploadedArchiveSize = 0
    [runners.cache.s3]
    [runners.cache.gcs]
    [runners.cache.azure]
  [runners.docker]
    tls_verify = false
    image = "alpine-latest"
    privileged = false
    volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
    disable_entrypoint_overwrite = false
    oom_kill_disable = false
    disable_cache = false
    shm_size = 0
    network_mode = "host"
    network_mtu = 0
```

Listo, solo queda reiniciarlo con un `docker restart gitlab-runner`. Tenemos el runner en linea y listo para funcionar.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763155084480/030cabd5-7427-4ad5-928a-611ca8a81958.png align="center")

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Es importante crear un repositorio en Docker Hub, que es donde guardamos la imagen. Agregar las variables <strong>DOCKERHUB_USERNAME</strong> &amp; <strong>DOCKERHUB_PASSWORD </strong>en el CI/CD. Mas adelante deberas agregar el variable de Defect Dojo y la configuracion de Kubernetes.</div>
</div>

Más adelante, deberás agregar la variable de Defect Dojo y la configuración de Kubernetes. Aquí tienes una imagen de ejemplo.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763160166690/26ca08f1-436c-426f-b714-3f1463ac4285.png align="center")

## Defect Dojo

Vamos a reunir todos nuestros hallazgos en esta plataforma. Una vez que esté en funcionamiento, la configuraremos para que esté operativa y podamos extraer la API Key que necesitamos para nuestro CI en Gitlab.

```basic
# Inicializar la base de datos de DefectDojo
docker exec -it defectdojo-uwsgi ./manage.py migrate

# Crear el superusuario (admin/admin123)
docker exec -it defectdojo-uwsgi ./manage.py createsuperuser --noinput --username admin --email admin@defectdojo.local

# Cambiar la password del admin
docker exec -it defectdojo-uwsgi python manage.py shell << EOF
from django.contrib.auth import get_user_model
User = get_user_model()
user = User.objects.get(username='admin')
user.set_password('admin123')
user.save()
EOF

# Reiniciar los servicios
docker restart defectdojo-uwsgi defectdojo-nginx
```

Generamos la API Key una vez que hicimos el Sign In.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763127860069/cac382f1-a0b2-496e-add1-a0ecd09a906c.png align="center")

La copiamos y luego la agregamos como variable en CI/CD bajo el nombre de `DEFECTDOJO_API_KEY`.

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">Es importante crear el Enviroment Development. Para que pueda subir los hallazgos el CI.</div>
</div>

## N8N

Importamos el proyecto y rellenamos lo que nos falta. Por un lado es el Token del Bot de Slack, en internet vas a encontrar como sacarlo, y por el otro un PRIVATE-TOKEN.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763160445795/f2b3b977-532e-4650-9ff1-48dd7d8fee3d.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763319569960/cbbbf378-361f-45ae-b657-3e14f0e1e46a.png align="center")

## Pipeline

Vamos a correr el pipeline. Hacemos un cambio y hacemos el push.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763317009588/2123c003-bfc6-415d-8e5f-270a599d54ac.png align="center")

Slack nos avisa, que tenemos que hacer. Le damos play al `deploy-k3s`. Listo, ya esta corriendo nuestro deployment.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763317049447/053a508f-a9c9-48ed-8ab8-aff5a833b7fd.png align="center")

Vemos como se despliega.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763317142785/64e4571b-dc5d-4cf8-b9b4-986833fe523b.png align="center")

El mensaje de Slack donde se ejecuto el pipeline numero #96.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763317195123/5814667f-bf4a-4407-b4cc-25d24f21ebbf.png align="center")

Ya estamos productivos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763317238747/316005cd-e652-48fe-94cf-00d4c6e829b0.png align="center")

## Generación de Errores

Ahora que el pipeline está listo, es momento de probar qué sucede si encuentra algo. Para esto, voy a crear un archivo con contraseñas para que **gitleaks** lo detecte y lo pase a **Ollama** para el análisis.

```bash
# Crear un archivo con un secreto
cat > app/config.py << 'EOF'
# Configuración de la aplicación
import os

# NUNCA hagas esto en producción
AWS_ACCESS_KEY = "AKIAIOSFODNN7EXAMPLE"
AWS_SECRET_KEY = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
DATABASE_PASSWORD = "super_secret_password_123"

# Configuración de la base de datos
DB_CONFIG = {
    "host": "localhost",
    "port": 5432,
    "username": "admin",
    "password": "admin123456",  # Contraseña hardcodeada
    "database": "production_db"
} 
EOF
```

Hacemos el commit.

```bash

# Commit
git add app/config.py
git commit -m "Change with Leaks"
git push
```

Aquí vemos el error y el análisis de Ollama. Si todo funciona correctamente, el Job de Bandit y GitLeaks encontrará problemas, lo que hará que el Security Gate falle.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763311722774/3cb1191d-e597-41a2-bda8-8f33b5ada0c8.png align="center")

Esto activará el **Webhook** a N8N para que Ollama lo analice y nos notifique por **Slack**. Recibimos el mensaje con el análisis realizado por la LLM. Es genial para saber dónde están los errores.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763317581472/63fc0ba7-3f6a-45e9-b383-05c2790918f2.png align="center")

Mientras tanto en Defect Dojo, tenemos una manera de visualizar los hallazgos.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1763312007019/30d68c72-2bba-4461-9493-b5ff8799f073.png align="center")

Espero que hayan disfrutado del proceso y puedan implementar IA en sus entornos. Seguiremos explorando esto, añadiendo **OWASP ZAP** y algunas otras cosas para interactuar con Slack.
