Tutorial
12 min read
51 views

Zero-Trust CI/CD: Construyendo Runners de GitHub Actions autoalojados y seguros mediante Túneles Inversos

IT
InstaTunnel Team
Published by our engineering team
Zero-Trust CI/CD: Construyendo Runners de GitHub Actions autoalojados y seguros mediante Túneles Inversos

Los equipos de ingeniería modernos enfrentan un dilema familiar: los runners de CI/CD en la nube son costosos y poco potentes para cargas exigentes, pero enrutar una plataforma pública como GitHub Actions a una red corporativa protegida por firewall suena a una pesadilla de TI. Existe una tercera vía — que utiliza túneles inversos solo salientes, contenedores efímeros y autenticación basada en identidad para ofrecer rendimiento de metal desnudo sin abrir un solo puerto entrante. Este artículo es un plan de referencia de nivel de producción para construir esa arquitectura.


El problema con “Simplemente abrir un puerto”

El enfoque convencional para runners autoalojados asume conectividad entrante. Un runner en tu red local se registra en GitHub y luego espera a que se le asignen trabajos. Para que esto funcione, GitHub necesita poder acceder a tu máquina — lo que generalmente implica abrir un agujero en tu firewall.

Este enfoque entra en conflicto casi de inmediato con la política de seguridad corporativa. Los puertos entrantes requieren cambios en las reglas del firewall, revisiones en la arquitectura de red y mantenimiento continuo de listas blancas de IP. GitHub opera desde rangos de IP dinámicos en múltiples centros de datos en todo el mundo — mantener y actualizar esos bloques CIDR es una carga operativa que no escala.

Hay un modelo más limpio: invertir completamente la dirección de la conexión.

Un runner autoalojado se comunica con GitHub mediante una sonde larga HTTP(S). Abre una conexión a GitHub por hasta 50 segundos esperando asignaciones de trabajo. Si no llega nada, se desconecta y vuelve a conectar. El tráfico es totalmente saliente en el puerto 443 — el mismo puerto que usa tu navegador para HTTPS. Los firewalls corporativos que permiten navegación web ya permiten este tráfico. No se requieren excepciones.


La arquitectura del túnel inverso

La clave es que el runner no necesita recibir conexiones entrantes de GitHub — sale y obtiene trabajo. Un agente de túnel inverso refuerza esta ventaja creando un canal saliente persistente y autenticado que también puede transportar cualquier webhook o tráfico de plano de control que deba llegar a tu máquina.

[ Hardware de metal desnudo local ]
        |
        | (TLS/QUIC saliente en puerto 443)
        ▼
[ Proxy de túnel con autenticación de identidad ]  ◄─── TLS mutuo / Verificación de tokens
        ▲
        |  (Webhook / Tráfico de sondeo)
        |
[ Plano de control de GitHub Actions ]

Dado que la conexión se inicia desde dentro de tu red, el firewall la ve como otra solicitud HTTPS. El proxy del túnel autentica ambos extremos antes de que fluya cualquier tráfico, y toda la asignación de trabajos ocurre sobre este canal verificado.


Seguridad basada en identidad: más que solo un túnel

Pasar tráfico a través de un túnel saliente solo es tan seguro como la autenticación que lo protege. Los despliegues en producción combinan varias capas:

TLS mutuo (mTLS): Tanto el cliente del runner como el proxy del túnel presentan certificados firmados criptográficamente. Ninguna de las partes acepta una conexión de un par no autenticado.

Vinculación OIDC / OAuth2: La sesión del túnel puede vincularse a proveedores de identidad verificados — Okta, Google Workspace, o tokens OIDC de la organización en GitHub — para que solo los runners de tu organización puedan establecer sesiones.

Tokens efímeros de corta duración: El token de registro usado para conectar un runner a GitHub se obtiene dinámicamente desde la API de GitHub al inicio de cada ejecución de pipeline y se invalida inmediatamente cuando termina el trabajo. No hay credenciales de larga duración en disco.

Estos controles significan que, aunque tu hardware local sea alcanzable vía el túnel, no es descubrible, escaneable ni accesible por nada que no pueda presentar la identidad criptográfica correcta.


El modelo de amenaza real (actualizado para 2025–2026)

Antes de construir esta infraestructura, vale la pena ser claros sobre qué puede salir mal.

En noviembre de 2025, el gusano Shai-Hulud demostró a escala que los runners autoalojados pueden ser utilizados como puertas traseras persistentes que comunican completamente a través de los canales confiables de GitHub. Debido a que todo el tráfico va a github.com, las defensas tradicionales de red son en gran medida ciegas ante esta clase de amenaza. La documentación de seguridad de GitHub es explícita: “Los runners autoalojados para GitHub no garantizan que se ejecuten en máquinas virtuales ephemeras limpias, y pueden ser comprometidos persistentemente por código no confiable en un workflow.”

Ataques anteriores a la cadena de suministro, incluyendo el envenenamiento de la acción tj-actions/changed-files y la brecha en Codecov, demostraron que los atacantes apuntan cada vez más a pipelines de CI/CD porque esas pipelines suelen tener acceso privilegiado a infraestructura de producción, credenciales en la nube y registros de paquetes.

La arquitectura descrita en este artículo está diseñada para contener estas amenazas — no ignorarlas.


Plan paso a paso

Componente 1: El contenedor efímero del runner

La primera regla de seguridad para runners autoalojados es que nunca deben ejecutar código no confiable directamente en el sistema operativo host. Un entorno en contenedor, de uso único, limita el radio de daño si algo sale mal.

eNota de versión: A partir del 16 de marzo de 2026, GitHub requiere que los runners autoalojados tengan al menos la versión v2.329.0 (lanzada el 15 de octubre de 2025). Los runners anteriores a esta versión son bloqueados en el registro. La versión estable actual a la fecha de este escrito es v2.334.0. Siempre descarga la última versión desde la página de lanzamientos de actions/runner.

FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update 66 apt-get install -y \
    curl \
    sudo \
    git \
    jq \
    build-essential \
    ca-certificates \
    66 rm -rf /var/lib/apt/lists/*

# Usuario runner sin privilegios
RUN useradd -m -s /bin/bash runner 66 \
    usermod -aG sudo runner 66 \
    echo "runner ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers

USER runner
WORKDIR /home/runner

# Fijar a una versión específica >= v2.329.0; revisa la página de lanzamientos para la última
ARG RUNNER_VERSION="2.334.0"

RUN curl -o github-runner.tar.gz -L \
    https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \
    66 tar xzf ./github-runner.tar.gz \
    66 rm github-runner.tar.gz

COPY --chown=runner:runner entrypoint.sh /home/runner/entrypoint.sh
RUN chmod +x /home/runner/entrypoint.sh

ENTRYPOINT ["./entrypoint.sh"]

Nunca debe usarse la bandera --privileged en el comando docker run para este contenedor. Un contenedor privilegiado tiene acceso casi total al host, similar a un proceso root en el sistema.

Componente 2: El script de entrada

El entrypoint.sh orquesta el registro, ejecución y limpieza. Obtiene un token de registro de corta duración desde la API de GitHub en cada invocación — no se almacena ningún token persistente en la imagen.

#!/bin/bash
set -e

if [ -z "$GH_OWNER" ] || [ -z "$GH_REPOSITORY" ] || [ -z "$GH_PAT" ]; then
    echo "ERROR: Faltan variables de entorno requeridas."
    exit 1
fi

echo "Obteniendo token de registro de corta duración..."
REG_TOKEN=$(curl -s -X POST \
    -H "Authorization: token ${GH_PAT}" \
    -H "Accept: application/vnd.github.v3+json" \
    https://api.github.com/repos/${GH_OWNER}/${GH_REPOSITORY}/actions/runners/registration-token \
    | jq -r '.token')

if [ "$REG_TOKEN" == "null" ] || [ -z "$REG_TOKEN" ]; then
    echo "ERROR: Falló al obtener el token de registro. Verifica permisos del PAT."
    exit 1
fi

./config.sh \
    --url https://github.com/${GH_OWNER}/${GH_REPOSITORY} \
    --token "${REG_TOKEN}" \
    --name "efímero-$(hostname)" \
    --labels "local-highperf,efímero" \
    --unattended \
    --replace

cleanup() {
    echo "Trabajo completo. Dando de baja el runner..."
    REM_TOKEN=$(curl -s -X POST \
        -H "Authorization: token ${GH_PAT}" \
        -H "Accept: application/vnd.github.v3+json" \
        https://api.github.com/repos/${GH_OWNER}/${GH_REPOSITORY}/actions/runners/registration-token \
        | jq -r '.token')
    ./config.sh remove --token "${REM_TOKEN}"
}

trap 'cleanup' EXIT SIGINT SIGTERM

# --once: aceptar un trabajo, ejecutarlo y salir
./run.sh --once

El flag --once es fundamental. Indica que el agente del runner debe aceptar exactamente un trabajo, ejecutarlo hasta completarlo y luego terminar. Combinado con un servicio systemd o cron que destruya el contenedor tras salir y lance uno nuevo desde la imagen base limpia, elimina estado persistente entre ejecuciones.

Componente 3: El agente del túnel

Con el contenedor del runner listo, un agente de túnel conecta tu red local con el plano de control de GitHub. Cloudflare Tunnel (cloudflared) es una opción de código abierto ampliamente usada que establece una conexión saliente autenticada contra tu cuenta de Cloudflare Zero Trust. Una configuración equivalente para frp u otros agentes sigue la misma estructura lógica.

Un ejemplo mínimo de tunnel.yaml para un despliegue de Cloudflare Tunnel:

tunnel: local-ci-runner-tunnel
credentials-file: /home/runner/.cloudflared/tunnel-auth.json

ingress:
  - hostname: ci-proxy.tuempresa.com
    service: http://localhost:8080
  - service: http_status:404

El hostname ci-proxy.tuempresa.com debe estar protegido por políticas de Cloudflare Access para que solo el tráfico webhook autenticado proveniente de los rangos de IP documentados de GitHub pueda enrutar a través del túnel. Todo lo demás recibe un 404.

El token del túnel debe pasarse como variable de entorno al lanzar el contenedor, nunca incrustarse en una imagen:

docker run --detach \
  --restart always \
  --network runner-net \
  --name cloudflared \
  cloudflare/cloudflared:latest \
  tunnel --no-autoupdate run --token "${TUNNEL_TOKEN}"

Endurecimiento de seguridad: las restricciones innegociables

Los túneles inversos eliminan el problema de exposición entrante, pero no protegen contra código malicioso que se ejecute dentro de tu perímetro una vez que comienza un trabajo. Los controles de endurecimiento siguientes son obligatorios en cualquier despliegue en producción.

1. Límites de aislamiento estrictos

El runner debe ejecutarse dentro de un contenedor, y ese contenedor debe correr en una máquina virtual dedicada cuando sea posible. Las microVMs Firecracker (usadas por Actuated y servicios similares) ofrecen aislamiento asistido por hardware: si ocurre una fuga del contenedor vía vulnerabilidad del kernel, el atacante aún queda contenido en una VM desechable sin acceso al sistema host ni a la red.

Requisitos mínimos: - No usar la bandera --privileged en los contenedores del runner - Usuario sin privilegios dentro del contenedor (como en el Dockerfile anterior) - Sin montajes de volúmenes que expongan rutas del sistema de archivos del host al entorno de construcción - Imagen del contenedor reconstruida desde una base limpia para cada trabajo; no reutilizar imágenes con estado en caché

2. Segmentación de red (sandboxing VLAN)

La máquina física que aloja el runner debe estar en una VLAN dedicada y aislada — un sandbox de desarrollo o segmento DMZ — con reglas de firewall que bloqueen explícitamente:

  • Todo tráfico desde la VLAN del runner hacia redes internas, comparticiones de archivos y clústeres de bases de datos
  • Todo tráfico saliente a internet excepto a los dominios específicos necesarios: *.github.com, *.githubusercontent.com, y los endpoints de tu proveedor de túnel

Esto evita que un runner comprometido sea usado para movimiento lateral dentro de tu red, incluso si las capas de aislamiento del contenedor y VM fallan.

3. Control de pull requests no confiables

La documentación de GitHub recomienda explícitamente no ejecutar runners autoalojados en repositorios públicos. Cualquier contribuyente que pueda bifurcar el repositorio y abrir un pull request puede potencialmente activar tu runner autoalojado. Para repositorios privados, aplica estos controles:

  • Requiere aprobación de un mantenedor antes de ejecutar workflows en pull requests de contribuyentes externos o bifurcaciones de primera vez
  • Usa reglas de protección de entorno y protección de ramas para que la etiqueta local-highperf solo pueda invocarse desde ramas protegidas (main, release/*)
  • Considera el runner como infraestructura completamente no confiable que podría ser comprometida en cualquier momento — la arquitectura debe diseñarse para ser segura en cualquier escenario

4. Los runners efímeros son innegociables

Los runners persistentes acumulan estado. Un trabajo que cachee binarios maliciosos globalmente, deje un proceso no autorizado en ejecución o modifique variables de entorno puede envenenar trabajos posteriores incluso de pull requests no relacionados. La bandera --once y el ciclo de destrucción y reconstrucción son la aplicación técnica del principio efímero.

Ten en cuenta que GitHub reconoce que este control “podría no ser tan efectivo como se pretende, ya que no hay forma de garantizar que un runner autoalojado solo ejecute un trabajo”. Esto no es una excusa para omitirlo — sigue elevando el nivel de seguridad.


La capa de autoscaling (actualización 2026)

GitHub introdujo en vista previa pública a principios de 2026 el Runner Scale Set Client — un módulo independiente en Go que permite a los equipos construir soluciones personalizadas de autoscaling para runners de GitHub Actions sin requerir Kubernetes. Esto es distinto del Actions Runner Controller (ARC), que sigue siendo la vía recomendada basada en Kubernetes.

Para entornos sin Kubernetes, el Scale Set Client permite construir orquestación basada en eventos que: - Escucha eventos de cola de trabajos mediante las APIs de escala de GitHub - Inicia un contenedor de runner nuevo en hardware disponible - Destruye el contenedor tras completar el trabajo

Esto proporciona capacidad elástica real en infraestructura fija — el hardware inactivo no cuesta en minutos de runner, y solo consumes capacidad cuando los builds están en ejecución.


Realidad del precio (2026)

La economía de los runners autoalojados cambió a principios de 2026. GitHub introdujo un cargo de $0.002 por minuto para uso de runners en repositorios privados, efectivo desde el 1 de marzo de 2026, cubriendo el plano de control de Actions (orquestación, programación, automatización de workflows). Este cargo se aplica independientemente de dónde estén alojados tus runners — en tu centro de datos, AWS o hardware propio.

Los repositorios públicos siguen siendo gratuitos. GitHub Enterprise Server no se ve afectado.

Lo que esto significa en la práctica: los runners autoalojados ya no son gratuitos desde GitHub, incluso si tu costo de computación es cero. Para un equipo que use 3,000 minutos al mes más allá de la cuota gratuita, el cargo en plataforma suma $2/mes — insignificante para la mayoría. Para entornos de CI de alto volumen, las matemáticas aún favorecen el hardware desnudo para cargas intensivas, pero el cargo en plataforma debe considerarse en tu modelo de costos.

Los precios de los runners hospedados por GitHub también bajaron aproximadamente un 40% el 1 de enero de 2026, lo que modifica algunas comparaciones con alternativas gestionadas.

Dimensión Runners hospedados en GitHub Hardware en metal desnudo vía Túnel Inverso
Costo de computación Por minuto (reducido ~40% en ene 2026) Capex fijo de hardware; costo marginal cero
Cargo de plataforma Incluido en el precio del runner $0.002/min para repos privados (desde marzo 2026)
Exposición entrante En infraestructura de GitHub Sin puertos entrantes necesarios
Control del hardware Tamaños T-shirt estándar Control total: Apple Silicon, clusters GPU, FPGAs
Aislamiento VMs efímeros gestionados Tu responsabilidad; requiere endurecimiento explícito
Costo en repos públicos Gratis Gratis

Rendimiento en el mundo real: Caso de compilación en macOS

Considera el ejemplo canónico: un equipo de desarrollo móvil compilando aplicaciones iOS. Los runners macOS con M2 de GitHub (disponibles en macos-latest-xlarge y etiquetas similares desde finales de 2025) son significativamente más rápidos que las opciones anteriores en la nube — pero aún corren en un entorno virtualizado con recursos compartidos.

Un Mac Studio inactivo con chip M2 Ultra en una oficina corporativa, configurado como runner autoalojado mediante un túnel de Cloudflare, ofrece ejecución nativa de metal desnudo. Los tiempos de compilación para una aplicación iOS representativa bajan de 30–45 minutos en runners en la nube a menos de 4 minutos en hardware local. Un año de desarrollo activo acumula miles de horas de tiempo de ingeniería recuperado.

La arquitectura descrita en este artículo hace ese hardware accesible a GitHub Actions sin cambios en tu firewall corporativo.


Resumen

La combinación de túneles inversos solo salientes, entornos efímeros en contenedores y autenticación basada en identidad resuelve la tensión central entre seguridad y rendimiento en CI/CD autoalojado:

  • Sin puertos entrantes. El runner inicia todas las conexiones salientes en el puerto 443.
  • Sin credenciales persistentes en disco. Los tokens de registro se obtienen dinámicamente y expiran inmediatamente.
  • Sin estado persistente. La bandera --once y la orquestación de destrucción y reconstrucción aseguran que cada trabajo se ejecute en un entorno limpio.
  • Sin movimiento lateral. La segmentación VLAN y reglas de firewall explícitas contienen el runner incluso si se compromete.
  • Sin sorpresas en costos. La tarifa de plataforma de 2026 es pequeña para la mayoría, pero debe modelarse con anticipación.

e Mantén tu versión del runner en v2.329.0 o superior — GitHub empezó a bloquear versiones anteriores en el registro desde marzo de 2026. Revisa la página de lanzamientos de actions/runner y actualiza tu ARG RUNNER_VERSION en el Dockerfile.

La arquitectura requiere configuración intencionada y mantenimiento continuo. Pero para equipos con cargas de trabajo intensivas, hardware especializado o requisitos de seguridad que exigen ejecución local, representa un camino probado y de nivel de producción.

Continue from this article into the most relevant product guides and workflows.

Related Topics

#self-hosted CI runner tunnel, secure GitHub Actions runner, local CI/CD hardware proxy, firewall bypass DevOps, ephemeral CI/CD runners, GitLab CI local tunnel, alternative to cloud hosted runners, self-hosted runner security 2026, on-premise build runners, bare-metal CI/CD proxy, reverse proxy for GitHub Actions, identity-gated tunnels, secure webhooks for CI/CD, connecting local hardware to GitHub, hybrid CI/CD pipeline, enterprise DevOps security, Apple Silicon CI runner, custom GPU hardware build runner, docker container build runner, ephemeral build agents, zero-trust pipeline networking, tunneling for webhooks, automated build pipeline proxy, cost-effective CI/CD scaling, infrastructure as code runner, private build network, remote build executor tunnel, self-hosted runner scaling, secure reverse tunnels, cloud infrastructure cost optimization

Keep building with InstaTunnel

Read the docs for implementation details or compare plans before you ship.

Share this article

More InstaTunnel Insights

Discover more tutorials, tips, and updates to help you build better with localhost tunneling.

Browse All Articles