egress.yml
La política es un fichero YAML del repositorio, .github/egress.yml salvo que la entrada policy indique otro.
version: 1
mode: enforce
presets: [github-actions]
allow:
- proxy.golang.org
- "*.npmjs.org"
- registry.example.com:5000
- ci-cache.example.com:*
inspect:
- api.staging.example.com| Clave | Por defecto | Significado |
|---|---|---|
version | 1 | Versión del formato. Solo existe la 1. |
mode | audit | audit: todo pasa y se informa de lo que enforce rechazaría. enforce: se rechaza lo que no está en allow. La entrada mode manda sobre ella. |
presets | ninguno | Listas con nombre que se mantienen con la acción. La única es github-actions. |
allow | ninguno | Destinos a los que pueden llegar el job y sus contenedores. |
inspect | ninguno | Hosts cuyo TLS termina el proxy para revisar los cuerpos de las peticiones en busca de secretos. Ver Inspección y DLP. |
Una clave desconocida es un error: una errata como alow: hace fallar el paso de la acción en vez de no permitir nada sin avisar. La acción comprueba el fichero antes de arrancar nada.
Reglas de allow
| Regla | Coincide con |
|---|---|
proxy.golang.org | Ese nombre, puertos 80 y 443 |
*.npmjs.org | Cualquier nombre por debajo de npmjs.org (registry.npmjs.org, a.b.npmjs.org), no npmjs.org en sí; puertos 80 y 443 |
registry.example.com:5000 | Ese nombre, solo el puerto 5000 |
registry.example.com:80,5000 | Ese nombre, puertos 80 y 5000 |
ci-cache.example.com:* | Ese nombre, cualquier puerto |
203.0.113.10 | Esa dirección IPv4, puertos 80 y 443 |
Los nombres no distinguen mayúsculas y el punto final se ignora. El comodín solo vale como primera etiqueta (*.example.com).
El job recibe las respuestas DNS de un nombre permitido; el bloqueo solo deja pasar los flujos que no son TCP (UDP como QUIC) hacia las direcciones de esas respuestas. Los pulls de imágenes se comprueban contra el nombre del registry en cualquier puerto.
El preset github-actions
github-actions es la lista que GitHub publica para los runners de Actions en el campo domains.actions de api.github.com/meta (enlace externo): el servicio del runner, el checkout, la API, la caché de Actions, los artefactos y los logs, con cada cuenta de almacenamiento de Azure por su nombre exacto (nunca *.blob.core.windows.net, donde cualquiera puede crear una cuenta). La acción añade raw.githubusercontent.com, donde las acciones setup-* leen sus manifiestos de versiones.
La acción descarga la lista al empezar con la entrada token y la guarda 6 horas en ~/.cache/egress-ci/ del usuario del runner. Si la descarga falla, usa una copia incluida en la acción. El resumen del job dice cuál se usó.
Entradas
| Entrada | Por defecto | Significado |
|---|---|---|
policy | .github/egress.yml | Ruta del fichero de política. |
mode | vacío | audit o enforce; manda sobre el mode del fichero. Sirve para probar un fichero en audit en las pull requests y aplicarlo en main. |
lock | auto | El bloqueo nftables para las conexiones que ignoran el proxy. auto: activo si el job llega a root con sudo o Docker, inactivo si no. on: como auto, pero el paso falla si no se llega a root. off: solo se usa HTTPS_PROXY. En enforce, un bloqueo que no se puede instalar hace fallar el paso. |
containers | track | track: el Docker del job pasa por su propio socket y sus contenedores y builds cumplen la política. lock-all: además rechaza en enforce el tráfico fuera de la política de cualquier otro contenedor (runners que ejecutan un job cada vez). off: Docker se deja como está. Ver Docker y builds. |
no-proxy | vacío | Entradas extra de NO_PROXY, separadas por comas, para hosts a los que las herramientas con proxy deben ir directas. Con el bloqueo activo, esas conexiones pasan igualmente por el proxy transparente y por la política. |
token | ${{ github.token }} | Token con el que se lee api.github.com/meta. Se le pasa a curl por la entrada estándar, nunca en la línea de órdenes. |
send-events | off | on: envía los destinos, bloqueos y detecciones del job a los eventos de Egress de tu organización. Ver Enviar los eventos a OpenWAF. |
openwaf-api | https://dash.openwaf.dev | API de OpenWAF que usa send-events. Tiene que ser https://. |
| Salida | Significado |
|---|---|
proxy | La URL del proxy, http://127.0.0.1:PUERTO. |
Lo que la acción deja al job
| Variable | Valor |
|---|---|
HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy | La URL del proxy. |
NO_PROXY, no_proxy | localhost,127.0.0.1,::1,172.16.0.0/12,.local,.localhost,.test, tu no-proxy y .blob.core.windows.net cuando el bloqueo está activo (la caché de Actions pasa entonces por el proxy transparente, limitada igualmente a las cuentas exactas de GitHub). |
EGRESS_CI_STATE | El directorio de estado del sidecar, $RUNNER_TEMP/egress-ci (ahí está el registro de eventos, events.jsonl). |
DOCKER_HOST, BUILDX_BUILDER | Con el seguimiento de Docker: el socket de Docker del job y el builder del job. |
NODE_EXTRA_CA_CERTS, SSL_CERT_FILE, REQUESTS_CA_BUNDLE, CURL_CA_BUNDLE, GIT_SSL_CAINFO | Con inspect: la CA de la ejecución, sola o añadida al bundle del sistema. |
El sidecar escucha en 127.0.0.1 (y en la dirección del bridge de Docker para los contenedores) en tres puertos por debajo del rango efímero, fijos por usuario del runner: el proxy, el forwarder DNS (el puerto siguiente) y el proxy transparente (el de después).
Decisiones y motivos
Cada conexión, consulta DNS y pull de imagen recibe una decisión: allow, audit (fuera de la política, se deja pasar porque el modo es audit) o block. Una rechazada lleva un motivo:
| Motivo | Por qué |
|---|---|
not_allowed | El nombre, o el puerto para ese nombre, no está en allow. |
sni_mismatch | Un CONNECT a un nombre permitido llevaba un SNI de TLS de un nombre fuera de la política. |
name_ip_mismatch | Una conexión nombraba un host permitido pero iba a una dirección que ese nombre no resuelve (domain fronting). |
dlp | La petición llevaba un secreto (ver Inspección y DLP). |
registry_not_allowed | Un pull de imagen de un registry fuera de la política. |
private_destination | Loopback, link-local (como la dirección de metadatos de la nube) o multicast, rechazados en los dos modos. |
En enforce, el proxy responde 403 con la cabecera X-OpenWAF-Egress: blocked (o dlp), el DNS responde NXDOMAIN, el socket de Docker responde 403 y el resto de conexiones recibe un reset.
El informe
El paso posterior añade el informe al resumen del job e imprime report.json en un grupo del log llamado egress-ci report.json. Contiene nombres, puertos, contadores e ids de detección, nunca cuerpos de peticiones.
| Campo de report.json | Significado |
|---|---|
meta | Versión, modo, fichero de política y su SHA-256, estado del bloqueo y de Docker, de dónde salió la lista de GitHub, horas de inicio y fin, los contenedores del job. |
destinations | Una entrada por host: ports, via, containers, connections, bytes_up, bytes_down, allowed, blocked, audited, dns_refused, reasons, detections. |
blocked, would_block, dns_refused, detections, dns_queries | Totales. |
suggested_allow | Nombres fuera de la política, listos para allow. |
image_pulls | Pulls por el socket del job, con su decisión. |
other_containers | Flujos de contenedores del runner que no son de este job. |
unattributed_addresses | Flujos que no son TCP hacia direcciones que ninguna respuesta DNS nombró. |