# Enviar los eventos a OpenWAF

> Envía los destinos, bloqueos y detecciones de cada job de CI a los eventos de Egress de tu organización con OIDC de GitHub, sin guardar un secreto en el repositorio.

Por defecto, la acción lo deja todo en la ejecución: el resumen del job y `report.json`. Con `send-events: on` también envía los destinos de cada job a tu organización de OpenWAF, donde se pueden leer por la API entre repositorios y ejecuciones.

No se guarda ningún secreto en el repositorio. El job demuestra quién es con un token OIDC de GitHub, que OpenWAF cambia por un token que solo le permite enviar los eventos de esa ejecución.

## Antes de empezar

- **Acceso anticipado.** Egress se activa por organización; hasta entonces, el registro responde `403 feature_disabled`. Escribe a support@openwaf.dev para entrar.
- **Un `admin` de toda la organización** para registrar repositorios. Para ver los repositorios registrados basta con cualquier rol de toda la organización.
- **Los ids de GitHub del repositorio**, que atan el registro a ese repositorio: un repositorio renombrado, transferido o borrado y vuelto a crear con el mismo nombre no es el mismo repositorio y hay que registrarlo de nuevo.

```sh
gh api repos/acme/app --jq '.id, .owner.id'
```

## 1. Registra el repositorio

Un repositorio de GitHub solo puede estar registrado en una organización. `refs` limita qué refs pueden enviar eventos (refs exactas, sin comodines, hasta 20); vacío quiere decir cualquiera.

**API**

```sh
curl -sS -X POST "https://dash.openwaf.dev/v1/orgs/$ORG/egress/ci/repositories" \
  -H "Authorization: Bearer $OPENWAF_TOKEN" -H "Content-Type: application/json" \
  -H "X-Change-Reason: CI egress events for acme/app" \
  -d '{"repository": "acme/app", "repository_id": 123456789, "repository_owner_id": 987654, "refs": ["refs/heads/main"]}'
```

Responde `201` con el registro (`id`, `repository`, `repository_id`, `repository_owner_id`, `refs`, `created_at`, `created_by`). Un `GET` a la misma ruta los lista en `items`; `DELETE …/egress/ci/repositories/{id}` borra uno (`204`), y los tokens que se le dieron dejan de valer al momento.
**CLI**

```sh
openwaf-cloud ctl egress ci-repos add acme/app --repository-id 123456789 --owner-id 987654 \
  --ref refs/heads/main --reason "CI egress events for acme/app"
openwaf-cloud ctl egress ci-repos list
openwaf-cloud ctl egress ci-repos rm acme/app --reason "repository archived"
```

`--dry-run` enseña el cambio sin hacerlo.


Los registros quedan en el [registro de auditoría](https://developers.openwaf.dev/es/change-control/audit-log/) con su motivo.

## 2. Actívalo en el workflow

```yaml
permissions:
  contents: read
  id-token: write   # permite al job pedir un token OIDC a GitHub
jobs:
  test:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@v5
      - uses: openwaf-dev/egress-action@v0.3.2
        with:
          send-events: "on"
      - run: make test
```

Al final del job, el resumen dice cuántos destinos se enviaron, por ejemplo `14 destinations sent to OpenWAF Egress events (https://dash.openwaf.dev).`

## Qué se envía

Un evento por destino del job, con la identidad de la ejecución. Un nombre que solo se resolvió, sin nada conectado, rechazado ni detectado, queda fuera.

| Parte | Campos |
|---|---|
| Ejecución | Repositorio, id e intento de la ejecución, workflow, job, SHA del commit, ref, nombre del evento, nombre del runner |
| Resumen | Modo, número de destinos, bloqueados, que se bloquearían, detecciones, consultas DNS |
| Cada destino | Host, primer puerto, vía (`proxy`, `direct`, `container`, `udp`, `dns`), nombre del contenedor, decisión (`allow`, `audit`, `block`), motivo, ids de detección, conexiones, bytes de subida y de bajada |

Nunca cuerpos de peticiones, cabeceras, URLs ni los secretos que encontró una detección.

## Cómo funciona

1. El paso posterior pide a GitHub un token OIDC con la audiencia `openwaf-egress-ci`. Nunca lo imprime ni lo registra.
2. Lo envía a `POST /v1/egress/ci/token` en `openwaf-api` (por defecto `https://dash.openwaf.dev`). OpenWAF comprueba la firma de GitHub, el emisor, la audiencia, la hora y que el token no se haya usado antes, y que el repositorio, su id y el id de su propietario coinciden con un registro (y la ref, cuando `refs` está puesto). Responde con un token (`OWE_CI_…`) válido 15 minutos y solo para ese repositorio.
3. Envía los eventos a `POST /v1/egress/ci/events` en lotes de hasta 1000, con hasta tres intentos por lote. Cada lote lleva un `batch_id`, así que un reintento nunca cuenta dos veces.

Un fallo nunca hace fallar el job: el resumen y el log reciben un aviso como `Events not sent to OpenWAF: token exchange: 403 repository_not_registered.`

| Error | Qué significa y cómo se arregla |
|---|---|
| `GitHub OIDC token: … (the job needs permissions: id-token: write)` | Añade `id-token: write` a los `permissions` del workflow o del job. Las pull requests de forks nunca lo tienen. |
| `401 invalid_token` | Se rechazó el token OIDC (firma, emisor, audiencia, hora o usado dos veces); o, en los eventos, caducó el token de OpenWAF. |
| `403 repository_not_registered` | Registra el repositorio; de nuevo si se renombró, se transfirió o se volvió a crear. |
| `403 ref_not_allowed` | La ref de la ejecución no está en los `refs` del registro. |
| `403 feature_disabled` | Egress no está activado para la organización. |
| `403 repository_mismatch` | El repositorio de la ejecución no es el del token. |
| `429 rate_limited`, `503 unavailable` | Inténtalo más tarde; el job no se ve afectado. |

## Ver los eventos

Los eventos de CI tienen su propio sitio, aparte de los rechazos de tus agentes de IA (la vista **Peticiones bloqueadas** del panel y la herramienta MCP `egress_events` solo muestran esos):

- **Panel:** Egress IA › **CI** lista los últimos 7 días agrupados por repositorio y ejecución, con el job, la vía, el contenedor, la decisión, el motivo, las detecciones y el tráfico de cada destino, y un enlace a la ejecución en GitHub. Se filtra por repositorio, decisión o texto libre.
- **MCP:** la herramienta `egress_ci_events` admite `repo`, `workflow`, `job`, `run`, `decision` (`allow`, `audit`, `block`) y `range` (`1h`, `24h`, `7d`, `30d`), y resume los eventos por repositorio y destino, con cuántos se bloquearon o se habrían bloqueado.
- **API:**

```sh
curl -sS "https://dash.openwaf.dev/v1/orgs/$ORG/egress/events?attack_type=egress_ci&ci_repo=acme/app&limit=100" \
  -H "Authorization: Bearer $OPENWAF_TOKEN"
```

`attack_type=egress_ci` selecciona los eventos de CI. `ci_repo`, `ci_workflow`, `ci_job` y `ci_run` filtran por repositorio, workflow, job e id de ejecución (exactos, repetibles; `not_ci_…` los excluye) y se combinan con `action`, `host`, `rule_name` y `q`. `…/egress/events/stream` admite los mismos filtros y sigue los nuevos como Server-Sent Events. En cada evento:

| Campo | Valor |
|---|---|
| `action` | `allow`, `log` (audit: enforce lo habría rechazado) o `block` |
| `rule_name` | El motivo, como `not_allowed` o `dlp` |
| `host` | El destino (e `ip` cuando el destino es una dirección) |
| `method` | La vía: `proxy`, `direct`, `container`, `udp` o `dns` |
| `path` | Puerto, contenedor y contadores, como `:443 container=db connections=3 bytes_up=812 bytes_down=6200` |
| `user_agent` | `ci:<propietario>/<repo>/<workflow>/<job>#<id de ejecución>` |
| `msg` | Los ids de detección |
