Saltar al contenido principal
intermediatePart 7

Limita la salida de un contenedor de agente para que solo alcance una API y nada más

· 23 min de lectura
Rafael Fernandes
Ingeniero de PLN y Redactor Técnico en WiLine
Share:
+

La parte 4 de orquestación de agentes dividió una caja de herramientas para que el agente de agenda no pudiera emitir reembolsos. La parte 5 le dio al agente una identidad propia en el gateway. La parte 6 puso un verificador de JWT delante del servidor de herramientas para que rechace a quien llama sin identificarse.

Todas ellas controlan qué puede llamar el agente. Ninguna controla adónde puede ir.

Esa distinción es todo este artículo. La exfiltración no necesita una herramienta. Necesita un socket.

Reproducibilidad

Una WEC Instance: 8 vCPU AMD EPYC 7601, 15 GB de RAM, Ubuntu 22.04, kernel 5.15. Docker 29.1.3 con backend de firewall iptables — que en esta distribución es iptables-nft v1.8.7, una capa que en realidad escribe en nftables. El contenedor de pruebas es alpine:3.20. UFW está activo.

Cada código de estado, cada mensaje de error y cada medición de este artículo salen de la ejecución descrita. HTTP plano en una LAN privada donde aparece la LAN, como en toda la serie.

Dos direcciones, y solo una suele estar vigilada​

Un firewall decide qué paquetes pueden cruzar un límite. Casi siempre la gente lo dice en una sola dirección — entrante: quién desde fuera puede llegar hasta tu servicio. Eso es lo que hace publicar un puerto, y de eso trataba la parte 1 de esta serie.

La salida es la otra dirección: adónde puede llegar tu servicio. A qué direcciones puede abrir una conexión y a cuáles no.

Casi nadie configura la salida, y para una aplicación web normal es una decisión razonable. Habla con su base de datos y con un proveedor de pagos, hace lo que dice su código, y ese código no cambia entre despliegues.

Un agente no es eso. Un agente decide qué hacer en tiempo de ejecución, a partir de texto que acaba de leer — un correo, un ticket de soporte, una página web, un documento que escribió otra persona. Si un atacante controla ese texto, influye en lo que el agente hace a continuación. Y lo que no quieres que haga a continuación es exfiltración: enviar tus datos a donde no deben ir.

La exfiltración no necesita ninguna herramienta especial. Cualquier proceso que pueda abrir un socket puede enviar cualquier cosa que pueda leer. Por eso la dirección que nadie vigila es justo la que importa aquí.

Lo que dicen realmente los estándares​

La referencia principal para este tipo de problema es LLM06:2025 Agencia Excesiva de OWASP. Su sección de prevención enumera, en orden: minimizar las extensiones, minimizar la funcionalidad de las extensiones, evitar extensiones abiertas, y algunos puntos más sobre permisos y aprobación humana.

Lee la página buscando la red y no está. Ni "egress", ni "firewall", ni "red", ni "aislamiento", ni "sandbox" — esas palabras no aparecen en la entrada.

La página de OWASP LLM06 Agencia Excesiva mostrando sus estrategias de prevención y mitigación, todas sobre herramientas y extensiones

Cada mitigación de esa lista opera sobre herramientas. Quita la extensión, acota la extensión, evita la extensión abierta. Todo es buen consejo. Nada de eso cambia un solo byte de lo que un proceso comprometido puede hacer con un socket TCP.

Hay quien defiende que esto es un hueco y no un descuido, y el argumento se está haciendo dentro del propio repositorio de OWASP. Un issue abierto que propone un mapeo de aplicación en tiempo de ejecución para el Top 10 Agéntico lo dice sin rodeos:

The enforcement pipeline assumes agents cannot reach tools without passing through the proxy. If tools are directly reachable, runtime enforcement collapses.

y concluye que "Network-level controls (e.g., Kubernetes NetworkPolicy, Docker network isolation) are required to close this gap in production."

Fíjate en qué es eso: un issue abierto, presentado por un colaborador, con su mapeo marcado como (Proposed). No es el estándar. Es alguien argumentando que el estándar debería decir esto.

Y la razón por la que esto importa no es teórica. EchoLeak (CVE-2025-32711) es una inyección de prompt indirecta de cero clics contra Microsoft 365 Copilot, registrada en NVD como "Ai command injection in M365 Copilot allows an unauthorized attacker to disclose information over a network." Over a network. La lista de herramientas nunca fue lo que falló.

Requisitos previos​

  • Docker en Linux con el backend de firewall iptables — el predeterminado. Compruébalo con docker info | grep -i "Firewall Backend"
  • sudo, y voluntad de escribir reglas de firewall en una máquina que aún puedas alcanzar
  • Nada de las partes anteriores. Esta se sostiene sola.
Lee esto antes de tocar DOCKER-USER

DOCKER-USER es estado real del firewall de tu host. Una regla que coincida con más de lo que pretendías dejará servicios fuera de línea. Todas las reglas de este artículo están acotadas con -i br-agent, una interfaz que pertenece a una red creada para este fin — que es justo por lo que en el paso 1 nombramos ese bridge a mano en lugar de dejar que Docker genere uno.

Si trabajas por SSH, ninguna de estas reglas toca la cadena INPUT, así que tu sesión no corre riesgo. Compruébalo tú mismo antes de creértelo.

Paso 1 — Demuestra el hueco​

Una red, con la interfaz del bridge nombrada explícitamente. Docker documenta la opción como "Interface name to use when creating the Linux bridge":

docker network create --driver bridge \
-o com.docker.network.bridge.name=br-agent agent-egress
ip -br link show br-agent
br-agent DOWN 8a:77:65:54:9b:0d <NO-CARRIER,BROADCAST,MULTICAST,UP>

DOWN, porque todavía no hay nada conectado. Una red de Docker es una interfaz que no se levanta hasta que se conecta un contenedor, y una regla con -i br-agent sobre una red inactiva no coincide con nada, en silencio. Conviene saberlo antes de pasar una hora depurando una política que nunca se ejecutó.

El contenedor de pruebas — con versión fijada, no latest:

docker run -d --name agent-sandbox --network agent-egress alpine:3.20 sleep infinity
docker exec agent-sandbox apk add --no-cache curl bind-tools

Ahora cuatro destinos. Fíjate en lo que no hay en este contenedor: ni agente, ni framework, ni cliente MCP, ni herramientas. Aquí no se está evadiendo el acotado de herramientas — es que no hay nada que evadir.

docker exec agent-sandbox curl -sS -m 8 -o /dev/null -w 'inference API HTTP %{http_code}\n' https://inference.wiline.com/v1/models
docker exec agent-sandbox curl -sS -m 8 -o /dev/null -w 'arbitrary host HTTP %{http_code}\n' https://example.com
docker exec agent-sandbox curl -sS -m 8 -o /dev/null -w 'authentik LAN HTTP %{http_code}\n' http://10.80.4.212:9100/
docker exec agent-sandbox curl -sS -m 8 -o /dev/null -w 'other network exit=%{exitcode} %{errormsg}\n' http://172.21.0.2:5432/
inference API HTTP 401
arbitrary host HTTP 200
authentik LAN HTTP 302
other network exit=28 Connection timed out after 5002 milliseconds

Cuatro sondas desde el contenedor de pruebas: la API de inferencia responde 401, example.com responde 200, authentik responde 302, y una conexión a otra red Docker expira

El 401 es el que conviene entender. No se envió ninguna clave de API, así que la petición nunca llega a nada que pudiera cobrarse — pero un 401 es una respuesta HTTP, lo que significa que el handshake TCP se completó, que TLS negoció, y que respondió un servidor real. Alcanzabilidad demostrada, gratis.

La cuarta línea es la que mantiene esto honesto. Ese es un contenedor de Postgres en otra red de Docker, y expira — la parte 1 de esta serie estableció que las redes bridge separadas sí aíslan, y esto lo confirma.

Así que la forma del hueco es precisa, no vaga:

DestinoResultado
Otra red de DockerexpiraDocker ya se encarga de esto
Tu propia LAN, incluido el proveedor de identidad302abierto
Internet abierto200abierto
La única API que de verdad necesita401abierto

Docker aísla los contenedores entre sí y ahí se detiene. Las dos direcciones que importan para la exfiltración — tu red e internet — están abiertas de par en par, y ninguna guía de Docker lo presenta como un hueco porque, para una aplicación web, no lo es.

Paso 2 — El suelo​

Una regla. Fíjate en -A y no -I: la denegación va al final de la cadena y se queda ahí, y cada permiso que añadamos después se apila encima. Ese orden es la política.

sudo iptables -A DOCKER-USER -i br-agent -j DROP

DOCKER-USER es donde esto corresponde, y la documentación de Docker dice por qué:

Rules appended to the FORWARD chain will be processed after Docker's rules.

DOCKER-USER se procesa antes que ellas — la documentación la describe como "a placeholder for user-defined rules that will be processed before rules in the DOCKER-FORWARD and DOCKER chains." En cualquier otro sitio, el ACCEPT del propio Docker llega primero.

Léelo de arriba abajo y la colocación se explica sola. DOCKER-FORWARD contiene un ACCEPT incondicional por cada bridge — eso es lo que hace que la salida de los contenedores funcione, y por eso una regla añadida a FORWARD nunca se dispara. DOCKER-USER es el único punto que queda por delante.

Repite las mismas cuatro sondas:

inference API exit=28 Resolving timed out after 8000 milliseconds
arbitrary host exit=28 Resolving timed out after 8002 milliseconds
authentik LAN exit=28 Connection timed out after 8003 milliseconds
other network exit=28 Connection timed out after 8002 milliseconds

La única regla DROP listada, y las cuatro sondas fallando con dos mensajes de error distintos

Lee el texto del error, no el fallo. Dos mensajes distintos, y la diferencia es información de diagnóstico gratuita:

  • "Resolving timed out" — el propio DNS quedó bloqueado. Nunca llegamos a saber la dirección.
  • "Connection timed out" — la dirección ya se conocía, el paquete salió, y el DROP lo cazó.

Las dos sondas por nombre murieron en la resolución; las dos por IP murieron en el socket. Lo que nos dice algo que no es obvio: el resolvedor integrado de Docker reenvía sus consultas a través de DOCKER-USER. El resolvedor vive en 127.0.0.11 dentro del espacio de nombres del contenedor, y es tentador suponer que ese tráfico nunca toca la ruta de forwarding del host. Sí la toca.

Fíjate también en que cada fallo tardó los ocho segundos completos. Un DROP produce expiraciones; un REJECT falla al instante. Sigilo contra facilidad de depuración, y este artículo elige el sigilo — pero si todavía estás iterando sobre una política, REJECT te ahorrará muchísima espera.

Paso 3 — La lista de permitidos​

Antes de escribir una regla para el resolvedor, averigua qué resolvedor usa realmente el contenedor:

docker exec agent-sandbox cat /etc/resolv.conf
nameserver 127.0.0.11
search corp.internal internal mesh.example.com
options edns0 trust-ad ndots:0

# Based on host file: '/etc/resolv.conf' (internal resolver)
# ExtServers: [8.8.8.8 8.8.4.4]

ExtServers: [8.8.8.8 8.8.4.4]. El contenedor no está usando el DNS de esta red. El host resuelve a través de systemd-resolved en 127.0.0.53, que es una dirección de loopback y por tanto inútil dentro del espacio de nombres de un contenedor, así que Docker sustituyó el DNS público de Google sin mencionarlo.

Mira los dominios de búsqueda heredados en el mismo bloque (aquí redactados, pero reales en tu máquina). Cada nombre interno que ese contenedor resuelve se le está preguntando a 8.8.8.8, con el sufijo de los dominios privados que heredó del host. Nadie eligió eso, y nada lo saca a la luz salvo leer este fichero.

También decide la regla: permite el resolvedor que el contenedor tiene, no el que supusiste.

getent hosts inference.wiline.com
67.207.107.229 inference.wiline.com
sudo iptables -I DOCKER-USER -i br-agent -p udp --dport 53 -d 8.8.8.8,8.8.4.4 -j ACCEPT
sudo iptables -I DOCKER-USER -i br-agent -p tcp --dport 53 -d 8.8.8.8,8.8.4.4 -j ACCEPT
sudo iptables -I DOCKER-USER -i br-agent -p tcp --dport 443 -d 67.207.107.229 -j ACCEPT
sudo iptables -S DOCKER-USER
-N DOCKER-USER
-A DOCKER-USER -d 67.207.107.229/32 -i br-agent -p tcp -m tcp --dport 443 -j ACCEPT
-A DOCKER-USER -d 8.8.4.4/32 -i br-agent -p tcp -m tcp --dport 53 -j ACCEPT
-A DOCKER-USER -d 8.8.8.8/32 -i br-agent -p tcp -m tcp --dport 53 -j ACCEPT
-A DOCKER-USER -d 8.8.4.4/32 -i br-agent -p udp -m udp --dport 53 -j ACCEPT
-A DOCKER-USER -d 8.8.8.8/32 -i br-agent -p udp -m udp --dport 53 -j ACCEPT
-A DOCKER-USER -i br-agent -j DROP

Cinco permisos sobre una denegación. Las mismas cuatro sondas, por tercera vez:

inference API HTTP 401 exit=0
arbitrary host exit=28 Connection timed out after 8002 milliseconds
authentik LAN exit=28 Connection timed out after 8003 milliseconds
other network exit=28 Connection timed out after 8001 milliseconds

Las cuatro sondas tras la lista de permitidos: la API de inferencia responde 401, y los otros tres destinos expiran en la fase de conexión y no en la de DNS

Mira la segunda línea. Dice "Connection timed out", no "Resolving timed out". El DNS está abierto, así que example.com sí resolvió a una dirección real — y entonces el paquete murió. La resolución funcionó y el socket no.

Ese es todo el artículo en una línea. Un proceso comprometido dentro de ese contenedor puede averiguar adónde enviar tus datos y no puede enviarlos.

La regla de conntrack que esto no necesita

Todas las guías sobre este tema te dicen que primero aceptes el tráfico RELATED,ESTABLISHED, y la documentación de Docker muestra la regla. Esta política no tiene ninguna, y el 401 de arriba demuestra que el handshake TLS completo y la respuesta HTTP volvieron igualmente.

La razón es la dirección. Nuestro DROP coincide con -i br-agent — tráfico que llega desde el contenedor. Los paquetes de vuelta llevan -o br-agent, nunca coinciden, atraviesan DOCKER-USER sin tocarse, y los aceptan después las propias reglas de seguimiento de conexiones de Docker.

Necesitas el ACCEPT de conntrack cuando tu DROP puede coincidir con la dirección de vuelta, que es el caso de los ejemplos de tráfico entrante de donde viene ese consejo. Un DROP de salida específico de dirección, no. Añádela igualmente si quieres — no cuesta nada — pero entiende que copiarla sin entenderla es como se llega a la conclusión de que las reglas de firewall son magia negra.

Paso 4 — Medir el coste, dos veces​

Una política que nadie mide es una política que alguien desactivará cuando haya carga. Así que: ¿cuánto cuestan seis reglas?

El enfoque obvio, veinte conexiones con las reglas puestas y veinte sin ellas:

for i in $(seq 20); do docker exec agent-sandbox curl -sS -o /dev/null -w '%{time_connect}\n' -m 8 https://inference.wiline.com/v1/models; done | sort -n | awk '{a[NR]=$1} END{printf "rules ON — median %.4fs min %.4fs max %.4fs (n=%d)\n", a[int(NR/2)+1], a[1], a[NR], NR}'
sudo iptables -F DOCKER-USER
rules ON — median 0.1573s min 0.0940s max 0.3768s (n=20)
rules OFF — median 0.0936s min 0.0911s max 0.1576s (n=20)

La medición ingenua mostrando una diferencia de mediana de 64 milisegundos entre reglas activas y reglas desactivadas

64 milisegundos. Lo cual sería un hallazgo serio, si fuera cierto.

No lo es. Seis reglas de coincidencia de paquetes son microsegundos de trabajo del kernel, y los mínimos delatan el truco: 0.0940 frente a 0.0911, casi idénticos. La ruta rápida es la misma en ambas ejecuciones. Toda la diferencia vive en la cola.

El time_connect de curl se mide desde el inicio de la petición y por tanto incluye la resolución del nombre. La primera ejecución pagó una ida y vuelta completa a 8.8.8.8. Para la segunda, el resolvedor ya tenía la respuesta en caché. Medimos el orden en que las ejecutamos.

Mide solo la conexión TCP, restando la resolución:

for i in $(seq 30); do docker exec agent-sandbox curl -sS -o /dev/null -w '%{time_namelookup} %{time_connect}\n' -m 8 https://inference.wiline.com/v1/models; done | awk '{print ($2-$1)}' | sort -n | awk '{a[NR]=$1} END{printf "connect-only median %.5fs min %.5fs max %.5fs (n=%d)\n", a[int(NR/2)+1], a[1], a[NR], NR}'
rules ON — connect-only median 0.07098s min 0.06999s max 0.08607s (n=30)
rules OFF — connect-only median 0.07102s min 0.06996s max 0.07214s (n=30)

La medición corregida mostrando una diferencia de mediana de cuatro centésimas de milisegundo, con la ruta protegida por el firewall nominalmente más rápida

Una diferencia de mediana de −0,04 ms. Negativa: la ruta protegida midió marginalmente más rápido, que es el aspecto que tiene el ruido. Todo cae dentro de unos ±40 µs.

Así que la respuesta honesta no es un número con un signo más delante. Es que el coste de esta política está por debajo de lo que este método puede resolver — y que la versión ingenua de la misma medición se equivocaba por tres órdenes de magnitud, en la dirección que te habría convencido de no desplegarla.

Paso 5 — Sobrevivir a un reinicio​

Las reglas de iptables viven en memoria. Ese -F del paso anterior le hizo a la política exactamente lo mismo que le hace un reinicio.

El reflejo es iptables-persistent, y aquí es la herramienta equivocada: guarda la tabla entera incluidas todas las reglas que generó Docker, y Docker vuelve a generarlas él mismo al arrancar. Acabas con duplicados y con un orden que nadie eligió.

Lo que quieres es una unidad que sea dueña solo de tus reglas y que se ejecute después de Docker:

/usr/local/sbin/agent-egress.sh
#!/usr/bin/env bash
# Lista de permitidos de salida para el bridge del agente. Se reaplica tras arrancar Docker.
set -euo pipefail

BR=br-agent
DNS="8.8.8.8 8.8.4.4"
API=$(getent hosts inference.wiline.com | awk '{print $1; exit}')

# Elimina solo nuestras propias reglas, para que sea seguro ejecutarlo dos veces.
# `|| true` importa: con la cadena limpia, grep sale con 1 y abortaría el script.
existing=$(iptables -S DOCKER-USER | grep " -i ${BR} " || true)
if [ -n "${existing}" ]; then
printf '%s\n' "${existing}" | sed 's/^-A /-D /' |
while read -r rule; do iptables ${rule} || true; done
fi

for d in ${DNS}; do
iptables -I DOCKER-USER -i "${BR}" -p udp --dport 53 -d "${d}" -j ACCEPT
iptables -I DOCKER-USER -i "${BR}" -p tcp --dport 53 -d "${d}" -j ACCEPT
done
iptables -I DOCKER-USER -i "${BR}" -p tcp --dport 443 -d "${API}" -j ACCEPT
iptables -A DOCKER-USER -i "${BR}" -j DROP
/etc/systemd/system/agent-egress.service
[Unit]
Description=Egress allowlist for the agent bridge
After=docker.service
Requires=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/agent-egress.sh

[Install]
WantedBy=multi-user.target
sudo chmod +x /usr/local/sbin/agent-egress.sh
sudo systemctl daemon-reload && sudo systemctl enable --now agent-egress.service

Demuestra que es idempotente reiniciándola y comparando la cadena consigo misma:

sudo systemctl restart agent-egress.service && sudo iptables -S DOCKER-USER

Las mismas seis reglas listadas tras dos reinicios consecutivos de la unidad, idénticas las dos veces

Seis reglas, dos veces, sin duplicados.

Dos cosas que esto te da de forma discreta. getent se ejecuta en cada arranque, así que la dirección de la API se vuelve a resolver al iniciar — no de forma continua, pero sí la mayor parte barata del problema del destino móvil, sin ningún demonio extra. Y el bucle de borrado elimina solo las reglas que coinciden con -i br-agent, así que la unidad nunca toca nada más de la cadena.

Habilidad desbloqueada 🏅

Puedes poner una política de denegación por defecto delante de un contenedor, permitir exactamente los destinos que necesita, distinguir un fallo de DNS de un fallo de socket leyendo el error, medir el coste sin engañarte, y hacer que todo sobreviva a un reinicio.

Si cambias Docker al backend de nftables​

Docker 29 incluye un backend de firewall nftables junto al de iptables. No es el predeterminado y este artículo no lo ejecutó — el cambio reconstruye todas las reglas del host, y verificar el comportamiento completo exige un reinicio. Lo que sigue viene de la documentación de Docker, y se marca como tal.

Todo lo anterior deja de aplicarse, porque:

In Docker's nftables implementation, there is no DOCKER-USER chain.

En su lugar creas tu propia tabla, y la prioridad hace el trabajo que hacía DOCKER-USER:

If your rules need to run before Docker's rules, give the base chains a lower priority number than Docker's chain.

Y una regla que bajo iptables sería definitiva, no lo es:

In nftables, an "accept" rule is not final. It terminates processing for its base chain, but the accepted packet will still be processed by other base chains, which may drop it.

Para sobreponerse al drop de Docker hace falta entonces --bridge-accept-fwmark.

La trampa que conviene conocer es la transición, y es silenciosa en ambos sentidos:

When starting the daemon with nftables after running with iptables, Docker will not remove the jump from the FORWARD chain to DOCKER-USER. So, rules created in DOCKER-USER will continue to run until the jump is removed or the host is rebooted. When starting with nftables, the daemon will not add the jump. So, unless there is an existing jump, rules in DOCKER-USER will be ignored.

Cambia de backend y tu política sigue funcionando — hasta el siguiente reinicio, cuando deja de hacerlo en silencio. iptables -S DOCKER-USER seguirá listando todas las reglas.

Resolución de problemas — los errores que produjo esta ejecución​

La unidad de systemd falla en su primera ejecución y funciona en la segunda​

status=1/FAILURE y nada más en el journal. La causa es la línea de limpieza: con la cadena vacía, grep no encuentra reglas coincidentes y sale con 1, y set -euo pipefail convierte eso en un aborto antes de añadir ninguna regla. La guarda de idempotencia rompe justo la única ejecución que no necesita idempotencia. Mira el || true del paso 5.

Todo expira, incluso lo que permitiste​

Comprueba si el fallo dice "Resolving" o "Connection". Si dice Resolving, el problema es tu regla de DNS y nunca se llegó a la regla del destino. Los contenedores a menudo no usan el resolvedor que esperas — lee /etc/resolv.conf dentro del contenedor, no en el host.

Las reglas están listadas pero no se filtra nada​

Comprueba que el bridge esté levantado: ip -br link show br-agent. Una red sin contenedores conectados tiene la interfaz DOWN, y una regla con -i sobre ella no coincide con nada.

Todo tarda exactamente ocho segundos en fallar​

Eso es DROP comportándose correctamente. Usa REJECT mientras iteras si prefieres fallar rápido.

La medición muestra decenas de milisegundos de sobrecoste​

Estás midiendo DNS. Usa %{time_connect} menos %{time_namelookup}, y sospecha de cualquier resultado donde los mínimos coincidan y las medianas no.

Qué te dio esto y qué no​

Hecho: un contenedor que puede alcanzar exactamente un destino y un resolvedor; un intento de exfiltración que falla después de una resolución DNS correcta y no antes; una política que se reaplica sola tras un reinicio y que vuelve a resolver su destino al hacerlo; y un coste medido indistinguible de cero.

Sin hacer:

  • El DNS sigue siendo un canal de exfiltración. Permitimos consultas a 8.8.8.8, y se pueden colar datos dentro de ellas. Cerrar eso significa un resolvedor propio y una regla que apunte solo a él — que es otro artículo.
  • La lista de permitidos es una IP, y las IP se mueven. La unidad vuelve a resolver al arrancar, que no es lo mismo que seguir a una CDN. Un proxy de reenvío que filtre por SNI de TLS es la respuesta honesta para un destino que rota; también es bastante más maquinaria.
  • Esto protege contenedores. Los agentes de la parte 3 de orquestación y de la parte 4 corren como procesos del host, donde estas reglas no aplican. Moverlos a un contenedor es el requisito previo, y merece la pena por sí mismo.
  • El host no está restringido, ni tampoco ningún otro contenedor de esta máquina. Otros cinco bridges siguen teniendo un ACCEPT incondicional en DOCKER-FORWARD.
  • Nada de esto detiene la inyección. Detiene la salida de la carga útil. Son trabajos distintos.
Finished this tutorial?
Mark it complete to earn Where the agent can go, not just what it can call on your skill path.

Qué viene después​

La secuela obvia es el resolvedor: monta uno propio, apunta el contenedor a él, permite el puerto 53 solo hacia esa dirección, y el canal de DNS se cierra junto con la dependencia de Google. También prepara el terreno para hacer bien las listas de permitidos por dominio, que es la parte del problema del destino móvil que este artículo deja abierta.

Lectura adicional​

Comments & questions

Hit an error, spotted a typo, or have a question? Leave a note below.