<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Dns on inetshell</title><link>https://inet.sh/es/tags/dns/</link><description>Recent content in Dns on inetshell</description><generator>Hugo</generator><language>es</language><lastBuildDate>Sat, 26 Sep 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://inet.sh/es/tags/dns/index.xml" rel="self" type="application/rss+xml"/><item><title>acme-dns: DNS-01 sin dar la llave de toda la zona (delegación CNAME de _acme-challenge) con certbot y cert-manager</title><link>https://inet.sh/es/posts/dns/acme-dns-dns-01-delegacion-cname/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/dns/acme-dns-dns-01-delegacion-cname/</guid><description>Para un certificado wildcard hace falta el reto DNS-01, y eso suele significar guardar en el servidor un token de API con permiso sobre toda la zona DNS. acme-dns es un servidor DNS mínimo que solo sabe responder TXT bajo su propio subdominio: delegas _acme-challenge.tu-dominio con un CNAME y cada cliente recibe credenciales que solo sirven para su registro. Instalación del servidor, registro con allowfrom, el hook acme-dns-auth.py de certbot (primera ejecución con --debug-challenges), el solver acmeDNS de cert-manager con su Secret JSON, y las trampas de TTL y de renovación.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<ol>
<li>
<p><strong>Delega una zona pequeña</strong> a la máquina donde correrá acme-dns (registros en tu DNS real):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">auth.example.com.   NS   acme.example.com.
</span></span><span class="line"><span class="cl">acme.example.com.   A    203.0.113.10
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><strong>acme-dns</strong> en esa máquina (<code>/etc/acme-dns/config.cfg</code>), escuchando DNS en el 53 y la API en el 443:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">general</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">listen</span> <span class="p">=</span> <span class="s2">&#34;0.0.0.0:53&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">protocol</span> <span class="p">=</span> <span class="s2">&#34;both&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">domain</span> <span class="p">=</span> <span class="s2">&#34;auth.example.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">nsname</span> <span class="p">=</span> <span class="s2">&#34;acme.example.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">nsadmin</span> <span class="p">=</span> <span class="s2">&#34;hostmaster.example.com&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">records</span> <span class="p">=</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;auth.example.com. A 203.0.113.10&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="s2">&#34;auth.example.com. NS acme.example.com.&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">api</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">ip</span> <span class="p">=</span> <span class="s2">&#34;0.0.0.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">port</span> <span class="p">=</span> <span class="s2">&#34;443&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">tls</span> <span class="p">=</span> <span class="s2">&#34;letsencrypt&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">disable_registration</span> <span class="p">=</span> <span class="kc">false</span>
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><strong>Registra</strong> un cliente y crea el CNAME:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -s -X POST https://acme.example.com/register -H <span class="s1">&#39;Content-Type: application/json&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  -d <span class="s1">&#39;{&#34;allowfrom&#34;: [&#34;203.0.113.0/24&#34;]}&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># {&#34;username&#34;:&#34;eabcdb41-…&#34;,&#34;password&#34;:&#34;pbAX…&#34;,&#34;fulldomain&#34;:&#34;d420c923-….auth.example.com&#34;,&#34;subdomain&#34;:&#34;d420c923-…&#34;,&#34;allowfrom&#34;:[&#34;203.0.113.0/24&#34;]}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">_acme-challenge.example.com.   CNAME   d420c923-….auth.example.com.
</span></span></code></pre></td></tr></table>
</div>
</div></li>
<li>
<p><strong>certbot</strong> con el hook:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">curl -fsSL -o /etc/letsencrypt/acme-dns-auth.py https://raw.githubusercontent.com/joohoi/acme-dns-certbot-joohoi/master/acme-dns-auth.py
</span></span><span class="line"><span class="cl">chmod +x /etc/letsencrypt/acme-dns-auth.py
</span></span><span class="line"><span class="cl">sed -i <span class="s1">&#39;s|^#!/usr/bin/env python$|#!/usr/bin/env python3|; s|^ACMEDNS_URL = .*|ACMEDNS_URL = &#34;https://acme.example.com&#34;|&#39;</span> /etc/letsencrypt/acme-dns-auth.py
</span></span><span class="line"><span class="cl">certbot certonly --manual --manual-auth-hook /etc/letsencrypt/acme-dns-auth.py <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --preferred-challenges dns --debug-challenges -d <span class="s1">&#39;*.example.com&#39;</span> -d example.com
</span></span></code></pre></td></tr></table>
</div>
</div><p>La primera vez el hook se registra solo, guarda las credenciales en <code>/etc/letsencrypt/acmedns.json</code> y <strong>se para</strong> para que crees el CNAME que imprime; Enter, y valida. Las renovaciones (<code>certbot renew</code>) ya no preguntan nada.</p>
</li>
</ol>
<h2 id="por-qué">Por qué</h2>
<p>DNS-01 es el único reto que da <strong>wildcards</strong> y el único que funciona para máquinas sin puerto 80 público. El precio habitual es un token de API de tu proveedor DNS en cada servidor que pide certificados; ese token normalmente puede cambiar <strong>cualquier registro de la zona</strong>, y vive en texto plano en <code>/etc/letsencrypt</code>.</p>
<p>acme-dns le da la vuelta: es un servidor DNS autoritativo de una zona propia (<code>auth.example.com</code>) que solo sabe servir registros TXT bajo subdominios aleatorios, con una API para actualizarlos. Como <code>_acme-challenge.example.com</code> es un <strong>CNAME</strong> hacia uno de esos subdominios, Let&rsquo;s Encrypt sigue la cadena y valida el TXT allí. Las credenciales de cada cliente solo sirven para su subdominio; robadas, lo peor que pasa es que alguien pida un certificado para ese nombre, no que reescriba tu zona.</p>
<h2 id="cert-manager">cert-manager</h2>
<p>El mismo esquema, con las credenciales en un Secret:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">kubectl -n cert-manager create secret generic acme-dns --from-file<span class="o">=</span>acmedns.json<span class="o">=</span>./acmedns.json
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;example.com&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;username&#34;</span><span class="p">:</span> <span class="s2">&#34;eabcdb41-d89f-4580-826f-3e62e9755ef2&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;password&#34;</span><span class="p">:</span> <span class="s2">&#34;&lt;password del registro&gt;&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;fulldomain&#34;</span><span class="p">:</span> <span class="s2">&#34;d420c923-bbd7-4056-ab64-c3ca54c9b3cf.auth.example.com&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;subdomain&#34;</span><span class="p">:</span> <span class="s2">&#34;d420c923-bbd7-4056-ab64-c3ca54c9b3cf&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;allowfrom&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;203.0.113.0/24&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">apiVersion</span><span class="p">:</span><span class="w"> </span><span class="l">cert-manager.io/v1</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">kind</span><span class="p">:</span><span class="w"> </span><span class="l">ClusterIssuer</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">metadata</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-dns</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">spec</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">acme</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">server</span><span class="p">:</span><span class="w"> </span><span class="l">https://acme-v02.api.letsencrypt.org/directory</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">email</span><span class="p">:</span><span class="w"> </span><span class="l">admin@example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">privateKeySecretRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">letsencrypt-dns-account-key</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">solvers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">dns01</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">acmeDNS</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">host</span><span class="p">:</span><span class="w"> </span><span class="l">https://acme.example.com</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="nt">accountSecretRef</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">acme-dns</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="nt">key</span><span class="p">:</span><span class="w"> </span><span class="l">acmedns.json</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>La clave del JSON es el dominio del certificado; para <code>*.example.com</code> y <code>example.com</code> vale la misma entrada (el CNAME <code>_acme-challenge.example.com</code> cubre ambos). Cada dominio distinto necesita su registro y su CNAME.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong><code>allowfrom</code> con la IP que ve acme-dns</strong>: la pública de salida del servidor o del clúster, no la del pod. Si falla el <code>/update</code> con 401, es esto o la contraseña.</li>
<li><strong>TTL del CNAME</strong>: ponlo bajo (60–300 s) la primera vez; con un TTL de una hora, un error en el CNAME tarda una hora en corregirse a ojos de Let&rsquo;s Encrypt. El TXT lo sirve acme-dns con TTL 1.</li>
<li><strong>Puerto 53 de verdad</strong>: acme-dns tiene que ser alcanzable por UDP y TCP desde Internet, y ser el <strong>NS de <code>auth.example.com</code></strong> en tu zona padre. Sin el registro NS, la delegación no existe y la validación devuelve NXDOMAIN.</li>
<li><strong>Un registro por dominio</strong>, no por servidor: si tres servidores piden certificados para <code>example.com</code>, comparten el mismo registro y el mismo <code>acmedns.json</code>, o cada uno registra el suyo y el CNAME solo puede apuntar a uno. Comparte las credenciales.</li>
<li><strong><code>--manual</code> renueva bien</strong> porque el hook está guardado en <code>/etc/letsencrypt/renewal/&lt;dominio&gt;.conf</code>; lo que no puedes es cambiar el hook sin volver a ejecutar <code>certonly</code>.</li>
<li><strong>Versión</strong>: acme-dns 2.0.2 es de febrero de 2025 y el proyecto sigue activo; el hook de certbot no ha cambiado en años y funciona con Python 3 tras corregir el shebang.</li>
</ul>
<p>Referencias: <a href="https://github.com/joohoi/acme-dns">acme-dns</a>, <a href="https://github.com/joohoi/acme-dns-certbot-joohoi">acme-dns-certbot-joohoi</a> y <a href="https://cert-manager.io/docs/configuration/acme/dns01/acme-dns/">ACMEDNS en cert-manager</a>.</p>
<h2 id="ver-también">Ver también</h2>
<ul>
<li><a href="/es/posts/kubernetes/cert-manager-http01-308-ingressclassname/">cert-manager: el reto HTTP-01 devuelve 308</a> — cuando HTTP-01 es suficiente y no lo es.</li>
<li><a href="/es/posts/pfsense/haproxy-acme-letsencrypt-cloudflare/">HAProxy en pfSense con ACME y Cloudflare</a> — DNS-01 con el token del proveedor, el caso que acme-dns evita.</li>
<li><a href="/es/posts/dns/dkim-2048-txt-255-caracteres/">DKIM de 2048 bits en un TXT partido</a> — otro registro DNS que se rompe por detalles.</li>
</ul>
]]></content:encoded></item><item><title>Certificado gestionado de Google que nunca se emite: CAA (pki.goog, letsencrypt.org), FAILED_NOT_VISIBLE y el orden correcto</title><link>https://inet.sh/es/posts/gcp/certificado-gestionado-google-caa/</link><pubDate>Sat, 26 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/gcp/certificado-gestionado-google-caa/</guid><description>El certificado gestionado de Google para un balanceador HTTPS lleva horas en PROVISIONING o cae a FAILED_NOT_VISIBLE o FAILED_CAA_FORBIDDEN. Las tres causas: el DNS todavía no apunta a la IP del balanceador, el certificado no está adjunto a un target proxy, o un registro CAA de la zona no autoriza a pki.goog ni a letsencrypt.org. Cómo comprobar el estado con gcloud, el CAA correcto, y Certificate Manager con autorización DNS para emitir antes de cambiar el DNS.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">example.com.   CAA 0 issue &#34;pki.goog&#34;
</span></span><span class="line"><span class="cl">example.com.   CAA 0 issue &#34;letsencrypt.org&#34;
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gcloud compute ssl-certificates describe web-cert --global --format<span class="o">=</span><span class="s1">&#39;yaml(managed)&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># managed:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   domainStatus:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#     www.example.com: PROVISIONING | ACTIVE | FAILED_NOT_VISIBLE | FAILED_CAA_FORBIDDEN | FAILED_CAA_CHECKING | FAILED_RATE_LIMITED</span>
</span></span><span class="line"><span class="cl"><span class="c1">#   status: PROVISIONING | ACTIVE</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Para que pase a <code>ACTIVE</code> tienen que cumplirse tres cosas a la vez: el <strong>A/AAAA</strong> del dominio apunta a la IP del balanceador (y a nada más), el certificado está <strong>adjunto a un target proxy</strong> con su forwarding rule, y ningún <strong>CAA</strong> de la zona prohíbe a las CAs de Google. Después, hasta <strong>60 minutos</strong>.</p>
<h2 id="por-qué">Por qué</h2>
<p>Google emite los certificados gestionados con <strong>Google Trust Services</strong> (<code>pki.goog</code>) o con <strong>Let&rsquo;s Encrypt</strong>, y elige una u otra en cada emisión o renovación. Para validar el dominio hace una comprobación HTTP contra la IP a la que resuelve el nombre, así que hasta que el DNS apunte al balanceador no hay nada que validar. Y como cualquier CA, respeta los registros <strong>CAA</strong>: si tu zona dice que solo <code>digicert.com</code> puede emitir (política corporativa típica), Google no puede.</p>
<p>Los estados lo cuentan:</p>
<table>
  <thead>
      <tr>
          <th><code>domainStatus</code></th>
          <th>Causa</th>
          <th>Qué hacer</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>PROVISIONING</code></td>
          <td>En curso</td>
          <td>Esperar; si pasa de 60 minutos, revisar lo de abajo</td>
      </tr>
      <tr>
          <td><code>FAILED_NOT_VISIBLE</code></td>
          <td>El nombre no resuelve a la IP del balanceador, o el certificado no está adjunto a un target proxy</td>
          <td>Corregir A/AAAA, adjuntar el certificado, esperar la propagación</td>
      </tr>
      <tr>
          <td><code>FAILED_CAA_CHECKING</code></td>
          <td>Google no pudo leer los CAA (DNS que no responde, DNSSEC roto)</td>
          <td>Arreglar la zona</td>
      </tr>
      <tr>
          <td><code>FAILED_CAA_FORBIDDEN</code></td>
          <td>Hay CAA y no incluye a <code>pki.goog</code> ni a <code>letsencrypt.org</code></td>
          <td>Añadir los dos registros</td>
      </tr>
      <tr>
          <td><code>FAILED_RATE_LIMITED</code></td>
          <td>Demasiados intentos de emisión</td>
          <td>Esperar; no recrear el certificado en bucle</td>
      </tr>
  </tbody>
</table>
<h2 id="el-caa-correcto">El CAA correcto</h2>
<p>Sin ningún registro CAA, cualquier CA puede emitir y Google funciona. Si tienes CAA (o tu organización lo exige), añade <strong>las dos</strong> CAs:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">example.com.   CAA 0 issue &#34;pki.goog&#34;
</span></span><span class="line"><span class="cl">example.com.   CAA 0 issue &#34;letsencrypt.org&#34;
</span></span></code></pre></td></tr></table>
</div>
</div><p>Poner solo una obliga a Google a usar siempre esa; la documentación lo desaconseja porque una renovación puede salir por la otra. Los CAA se heredan hacia abajo: un CAA en <code>example.com</code> cubre <code>www.example.com</code> si el subdominio no tiene el suyo.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">dig +short CAA example.com
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="certificate-manager-emitir-antes-de-cambiar-el-dns">Certificate Manager: emitir antes de cambiar el DNS</h2>
<p>Con los certificados clásicos (<code>compute ssl-certificates</code>) el dominio tiene que apuntar ya al balanceador, lo que en una migración significa unos minutos de HTTPS roto. <strong>Certificate Manager</strong> resuelve eso con <strong>autorización DNS</strong>: publicas un CNAME de validación y Google emite el certificado mientras el tráfico sigue en el sitio viejo.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gcloud certificate-manager dns-authorizations create www-auth --domain<span class="o">=</span>www.example.com
</span></span><span class="line"><span class="cl">gcloud certificate-manager dns-authorizations describe www-auth --format<span class="o">=</span><span class="s1">&#39;yaml(dnsResourceRecord)&#39;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># dnsResourceRecord: name: _acme-challenge.www.example.com. type: CNAME data: xxxx.authorize.certificatemanager.goog.</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># publica ese CNAME en tu zona y luego:</span>
</span></span><span class="line"><span class="cl">gcloud certificate-manager certificates create www-cert <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --domains<span class="o">=</span>www.example.com --dns-authorizations<span class="o">=</span>www-auth
</span></span><span class="line"><span class="cl">gcloud certificate-manager maps create web-map
</span></span><span class="line"><span class="cl">gcloud certificate-manager maps entries create www --map<span class="o">=</span>web-map --certificate<span class="o">=</span>www-cert --hostname<span class="o">=</span>www.example.com
</span></span><span class="line"><span class="cl">gcloud compute target-https-proxies update web-proxy --certificate-map<span class="o">=</span>web-map
</span></span></code></pre></td></tr></table>
</div>
</div><p>Cuando el certificado esté <code>ACTIVE</code>, cambias el A/AAAA y el corte es cero. Los CAA aplican igual.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>Cloudflare u otro proxy delante</strong> del balanceador: el A apunta al proxy, no a Google, y la validación falla con <code>FAILED_NOT_VISIBLE</code>. Nube gris durante la emisión, o Certificate Manager con autorización DNS.</li>
<li><strong>Un certificado gestionado no adjunto a nada no se emite.</strong> Crearlo y esperar no hace nada; adjúntalo al target proxy primero.</li>
<li><strong>Cambiar la lista de dominios</strong> crea un certificado nuevo: adjunta el nuevo, espera <code>ACTIVE</code>, y solo entonces quita el viejo del proxy, o tendrás un hueco.</li>
<li><strong>Wildcards</strong> solo con Certificate Manager y autorización DNS; los certificados gestionados clásicos no los admiten.</li>
<li><strong>DNSSEC mal firmado</strong> rompe la lectura de CAA: <code>FAILED_CAA_CHECKING</code> aunque no tengas ningún CAA.</li>
</ul>
<p>Referencia: <a href="https://docs.cloud.google.com/load-balancing/docs/ssl-certificates/google-managed-certs">Google-managed SSL certificates</a> y <a href="https://docs.cloud.google.com/certificate-manager/docs/overview">Certificate Manager</a> en la documentación de Google Cloud.</p>
]]></content:encoded></item><item><title>DKIM de 2048 bits: la llave no cabe en un TXT de 255 caracteres</title><link>https://inet.sh/es/posts/dns/dkim-2048-txt-255-caracteres/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/dns/dkim-2048-txt-255-caracteres/</guid><description>Cómo publicar una llave DKIM de 2048 bits (unos 410 caracteres) cuando el DNS rechaza el TXT por pasar de 255: partirla en varias cadenas entre comillas dentro del mismo registro. Comandos para Route 53 (CharacterStringTooLong), Google Cloud DNS, BIND y Terraform, y verificación con dig y openssl.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="nv">PUB</span><span class="o">=</span><span class="k">$(</span>openssl rsa -in dkim.key -pubout -outform DER 2&gt;/dev/null <span class="p">|</span> base64 <span class="p">|</span> tr -d <span class="s1">&#39;\n&#39;</span><span class="k">)</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;v=DKIM1; k=rsa; p=</span><span class="nv">$PUB</span><span class="s2">&#34;</span> <span class="p">|</span> fold -w <span class="m">255</span> <span class="p">|</span> sed <span class="s1">&#39;s/.*/&#34;&amp;&#34;/&#39;</span> <span class="p">|</span> paste -sd<span class="s1">&#39; &#39;</span> -
</span></span></code></pre></td></tr></table>
</div>
</div><p>Sale el valor listo para pegar en el proveedor de DNS, dos cadenas entre comillas separadas por un espacio:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&#34;v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvs7EROOYh35C/1s7M//pIaRvu1XWXzdexgBR2dO7eq1oDw+X08BNJUVUEte3z7os5CNeP7YfZrAqyDJvA59wMK1qul6oHyy7z5P1aB4zNczvO7SZ20HogWjHjzYJ0qwmSUUtjlKvECQzoTCbS/F0gB9rdUZN02MJopDs8YWcohy8oqNLnrflHdxWm6jAb3sSa&#34; &#34;EQReu6iRKKPiXTGUISSetY+iXLo2j/2c5g5ppiIYkUTq58asamMNiCzB7BeF4WNkjz2P+9IJmmtkkJ0M/U4oThUMvBBtQ/XUV4i2N4PGB359G5d/PIMiCj4go2axA4p8ul7Qafbtv0fyPwOIGSojwIDAQAB&#34;
</span></span></code></pre></td></tr></table>
</div>
</div><p>Las dos van en <strong>el mismo registro TXT</strong>. Quien verifica DKIM las concatena sin espacios. Si el texto te lo dio Google Workspace o Microsoft 365 y no tienes la llave privada, es lo mismo sin el <code>openssl</code>: <code>echo &quot;&lt;valor&gt;&quot; | fold -w 255 | sed 's/.*/&quot;&amp;&quot;/' | paste -sd' ' -</code>.</p>
<h2 id="el-problema">El problema</h2>
<p>Un registro TXT se compone de una o más <em>character-strings</em> y cada una tiene un tope de <strong>255 bytes</strong> (RFC 1035). El registro entero puede ser mucho más largo; lo que no puede es tener una sola cadena de 410 caracteres.</p>
<table>
  <thead>
      <tr>
          <th>Llave RSA</th>
          <th>Largo de <code>p=</code></th>
          <th>Con <code>v=DKIM1; k=rsa; p=</code></th>
          <th>¿Cabe en una cadena?</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1024 bits</td>
          <td>216</td>
          <td>234</td>
          <td>Sí</td>
      </tr>
      <tr>
          <td>2048 bits</td>
          <td>392</td>
          <td>410</td>
          <td>No: hacen falta dos</td>
      </tr>
      <tr>
          <td>4096 bits</td>
          <td>736</td>
          <td>754</td>
          <td>No: tres</td>
      </tr>
  </tbody>
</table>
<p>Google Workspace y Microsoft 365 generan llaves de 2048 por defecto, y con razón: 1024 está desaconsejado desde la RFC 8301. Así que hoy casi cualquier DKIM nuevo tropieza con esto, y cada proveedor reacciona distinto:</p>
<table>
  <thead>
      <tr>
          <th>Proveedor</th>
          <th>Qué pasa con 410 caracteres seguidos</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Route 53</td>
          <td>Rechaza con <code>CharacterStringTooLong</code>, en consola y CLI</td>
      </tr>
      <tr>
          <td>Google Cloud DNS</td>
          <td>Rechaza el valor; tienes que escribir tú las cadenas entre comillas</td>
      </tr>
      <tr>
          <td>Cloudflare</td>
          <td>Lo acepta y lo parte solo en trozos de 255</td>
      </tr>
      <tr>
          <td>BIND y la mayoría de servidores propios</td>
          <td>El archivo de zona no carga hasta que uses varias cadenas</td>
      </tr>
  </tbody>
</table>
<p>La RFC 6376 (§3.6.2.2) obliga a los verificadores a concatenar las cadenas antes de usarlas, así que partir la llave no es un truco: es el formato previsto.</p>
<h2 id="por-proveedor">Por proveedor</h2>
<h3 id="bind--archivo-de-zona">BIND / archivo de zona</h3>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">selector._domainkey  300  IN  TXT  ( &#34;v=DKIM1; k=rsa; p=MIIBIjANBgkq...primera parte&#34;
</span></span><span class="line"><span class="cl">                                     &#34;segunda parte...IDAQAB&#34; )
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="route-53">Route 53</h3>
<p>En la consola, en <strong>Value</strong> pega las dos cadenas entre comillas separadas por un espacio o un salto de línea. Por CLI el valor es una sola string con las comillas escapadas:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">aws route53 change-resource-record-sets --hosted-zone-id Z0123456789EXAMPLE --change-batch <span class="s1">&#39;{
</span></span></span><span class="line"><span class="cl"><span class="s1">  &#34;Changes&#34;: [{&#34;Action&#34;: &#34;UPSERT&#34;, &#34;ResourceRecordSet&#34;: {
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;Name&#34;: &#34;selector._domainkey.example.com&#34;, &#34;Type&#34;: &#34;TXT&#34;, &#34;TTL&#34;: 300,
</span></span></span><span class="line"><span class="cl"><span class="s1">    &#34;ResourceRecords&#34;: [{&#34;Value&#34;: &#34;\&#34;v=DKIM1; k=rsa; p=...primera parte\&#34; \&#34;segunda parte...\&#34;&#34;}]
</span></span></span><span class="line"><span class="cl"><span class="s1">  }}]}&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="google-cloud-dns">Google Cloud DNS</h3>
<p>En la consola, mismo formato en el campo de datos. Con <code>gcloud</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gcloud dns record-sets create selector._domainkey.example.com. --zone<span class="o">=</span>example-zone <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --type<span class="o">=</span>TXT --ttl<span class="o">=</span><span class="m">300</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  --rrdatas<span class="o">=</span><span class="s1">&#39;&#34;v=DKIM1; k=rsa; p=...primera parte&#34; &#34;segunda parte...&#34;&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Con Terraform (<code>google_dns_record_set</code>), un solo elemento en <code>rrdatas</code> con las comillas escapadas:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-hcl" data-lang="hcl"><span class="line"><span class="cl"><span class="n">rrdatas</span> <span class="o">=</span><span class="n"> [&#34;\&#34;v</span><span class="o">=</span><span class="n">DKIM1; k</span><span class="o">=</span><span class="n">rsa; p</span><span class="o">=</span><span class="p">...</span><span class="k">primera</span> <span class="k">parte</span><span class="err">\</span><span class="s2">&#34; \&#34;segunda parte...\&#34;&#34;</span><span class="p">]</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="cloudflare">Cloudflare</h3>
<p>Pega el valor completo en <strong>Content</strong>; el panel lo parte al guardar. Al consultarlo con <code>dig</code> verás dos cadenas aunque tú metiste una: es normal.</p>
<h2 id="verificar">Verificar</h2>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">dig +short TXT selector._domainkey.example.com
</span></span></code></pre></td></tr></table>
</div>
</div><p>Lo esperado es <code>&quot;v=DKIM1; k=rsa; p=MIIB...&quot; &quot;...IDAQAB&quot;</code>: dos cadenas, en un solo registro. Después comprueba que la llave decodifica a 2048 bits, lo que confirma que no se perdió ningún carácter en el corte:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">dig +short TXT selector._domainkey.example.com <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> tr -d <span class="s1">&#39;&#34; \n&#39;</span> <span class="p">|</span> sed -E <span class="s1">&#39;s/.*p=([A-Za-z0-9+/=]+).*/\1/&#39;</span> <span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="se"></span>  <span class="p">|</span> base64 -d <span class="p">|</span> openssl pkey -pubin -inform DER -noout -text <span class="p">|</span> head -1
</span></span><span class="line"><span class="cl"><span class="c1"># Public-Key: (2048 bit)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Si <code>base64</code> protesta, se coló un espacio dentro de la llave o faltan caracteres. Cierra con un correo de prueba y <code>Authentication-Results: ... dkim=pass</code> en la cabecera; para mirarlo con más detalle, el <a href="https://dmarcian.com/dkim-inspector/">DKIM Inspector de dmarcian</a>.</p>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>El corte puede ir en cualquier sitio</strong>, incluso a media base64, pero <strong>sin meter espacios dentro de la llave</strong>. El espacio entre las dos cadenas separa cadenas, no forma parte del dato.</li>
<li><strong>No dupliques comillas.</strong> Si el panel ya las pone por ti y pegas <code>&quot;...&quot;</code>, <code>dig</code> devuelve <code>&quot;\&quot;v=DKIM1...\&quot;&quot;</code> y el verificador falla.</li>
<li><strong>Un registro, no dos.</strong> Dos TXT distintos con el mismo nombre son dos valores independientes, y ninguno es una llave válida.</li>
<li><strong>2048 es el punto medio.</strong> 4096 son tres cadenas y una respuesta DNS que se acerca al límite UDP; funciona, pero no gana nada para DKIM.</li>
<li><strong>Rotación</strong>: publica la llave nueva con otro selector, cambia el firmante, y borra el selector viejo después del TTL. Nunca edites el selector en uso.</li>
<li><strong>SPF tiene el mismo límite</strong>, pero un SPF que no cabe en 255 suele ser señal de demasiados <code>include:</code>; ahí la solución es acortarlo, no partirlo.</li>
</ul>
<p>Referencias: <a href="https://knowledge.workspace.google.com/admin/security/troubleshoot-dkim-issues">Google Workspace — problemas con DKIM</a>, <a href="https://repost.aws/knowledge-center/route53-resolve-dkim-text-record-error">AWS re:Post — CharacterStringTooLong</a> y el <a href="https://www.mailhardener.com/tools/dns-record-splitter">DNS record splitter de Mailhardener</a> si prefieres no usar <code>fold</code>.</p>
]]></content:encoded></item><item><title>pfSense y OPNsense: el port forward no funciona desde la propia LAN (NAT reflection y split DNS)</title><link>https://inet.sh/es/posts/pfsense/nat-reflection-port-forward-desde-lan/</link><pubDate>Fri, 25 Sep 2026 00:00:00 +0000</pubDate><guid>https://inet.sh/es/posts/pfsense/nat-reflection-port-forward-desde-lan/</guid><description>Desde fuera el port forward funciona, pero desde la LAN la IP pública o el dominio te llevan a la GUI del firewall o a un error de certificado. Dos arreglos: split DNS con host overrides en el DNS Resolver (el recomendado) o NAT reflection en modo Pure NAT con outbound NAT automático. Incluye el equivalente en OPNsense y la trampa del DNS rebinding.</description><content:encoded><![CDATA[<h2 id="en-corto">En corto</h2>
<p><strong>Opción limpia (split DNS):</strong> Services → DNS Resolver → <em>Host Overrides</em> → <code>app.example.com</code> → IP interna del servidor (<code>192.168.1.50</code>). Los clientes de la LAN tienen que usar el firewall como DNS.</p>
<p><strong>Opción NAT reflection:</strong> System → Advanced → <em>Firewall &amp; NAT</em>:</p>
<ul>
<li><em>NAT Reflection mode for port forwards</em>: <strong>Pure NAT</strong></li>
<li><em>Enable NAT Reflection for 1:1 NAT</em>: ✔</li>
<li><em>Enable automatic outbound NAT for Reflection</em>: ✔</li>
</ul>
<p>Save y prueba desde un cliente de la LAN contra la IP pública.</p>
<h2 id="el-problema">El problema</h2>
<p>El port forward <code>WAN:443 → 192.168.1.50:443</code> funciona desde Internet. Desde un equipo de la misma LAN, <code>https://app.example.com</code> (que resuelve a la IP pública) responde con la GUI de pfSense, un error de certificado, o <code>Potential DNS Rebind attack detected</code>.</p>
<p>El paquete sale del cliente hacia la IP pública, llega al firewall por la interfaz LAN y ahí se queda: la regla de port forward escucha en la WAN, no en la LAN, y la IP de destino es una dirección del propio firewall. Nunca &ldquo;sale y vuelve a entrar&rdquo;.</p>
<h2 id="opción-1-split-dns">Opción 1: split DNS</h2>
<p>Es la que recomienda Netgate: el nombre resuelve a la IP interna dentro y a la pública fuera. El tráfico va directo, el servidor ve la IP real del cliente y el firewall no toca nada.</p>
<ol>
<li>Services → DNS Resolver → <em>Host Overrides</em> → <strong>Add</strong>: host <code>app</code>, dominio <code>example.com</code>, IP <code>192.168.1.50</code>. Si el servicio tiene varios nombres, añade <em>Additional Names</em>.</li>
<li>Los clientes deben usar el firewall como DNS (lo normal si el DHCP lo entrega). Un cliente con <code>8.8.8.8</code> fijo seguirá yendo a la IP pública.</li>
<li><code>nslookup app.example.com</code> desde la LAN → <code>192.168.1.50</code>.</li>
</ol>
<p>Requisitos: el puerto interno y el externo tienen que ser el mismo (si fuera reenvías <code>8443 → 443</code>, el cliente interno tendrá que usar <code>443</code> directamente).</p>
<p>Si usas DNS Forwarder (dnsmasq) en vez del Resolver, el ajuste equivalente está en Services → DNS Forwarder → <em>Host Overrides</em>.</p>
<h2 id="opción-2-nat-reflection">Opción 2: NAT reflection</h2>
<p>Cuando no controlas el DNS de los clientes, o hay un montón de puertos, el firewall puede &ldquo;reflejar&rdquo; la conexión: reescribe el destino a la IP interna y, con el outbound NAT automático, también el origen, para que la respuesta vuelva por el firewall.</p>
<p>System → Advanced → <em>Firewall &amp; NAT</em>, sección <em>Network Address Translation</em>:</p>
<table>
  <thead>
      <tr>
          <th>Ajuste</th>
          <th>Valor</th>
          <th>Por qué</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>NAT Reflection mode for port forwards</td>
          <td><strong>Pure NAT</strong></td>
          <td>Solo reglas de PF: todos los protocolos, sin límite de puertos. <em>NAT + Proxy</em> usa un daemon, solo TCP y máximo 500 puertos por regla</td>
      </tr>
      <tr>
          <td>Enable NAT Reflection for 1:1 NAT</td>
          <td>✔</td>
          <td>Si tienes 1:1 NAT además de port forwards</td>
      </tr>
      <tr>
          <td>Enable automatic outbound NAT for Reflection</td>
          <td>✔</td>
          <td>Sin esto, si cliente y servidor están en la misma subred el servidor responde directo al cliente, que no reconoce la conexión y la corta</td>
      </tr>
      <tr>
          <td>Reflection Timeout</td>
          <td>(solo NAT + Proxy)</td>
          <td>Ignóralo en Pure NAT</td>
      </tr>
  </tbody>
</table>
<p>También se puede activar por regla: Firewall → NAT → Port Forward → editar → <em>NAT reflection</em> → <em>Enable (Pure NAT)</em>, dejando el global en <em>Disable</em>.</p>
<p>Costes: el servidor ve como origen la IP LAN del firewall (no la del cliente), y todo ese tráfico pasa por el firewall aunque cliente y servidor estén en el mismo switch.</p>
<h2 id="opnsense">OPNsense</h2>
<p>Misma idea, otros menús:</p>
<ul>
<li>Split DNS: Services → Unbound DNS → <em>Overrides</em> → Host Overrides.</li>
<li>Reflection: Firewall → Settings → <em>Advanced</em> → <strong>Reflection for port forwards</strong>, <strong>Reflection for 1:1</strong>, <strong>Automatic outbound NAT for Reflection</strong>.</li>
</ul>
<h2 id="trampas">Trampas</h2>
<ul>
<li><strong>DNS rebinding.</strong> Si en vez de un host override apuntas el Resolver a un DNS externo que devuelve IPs privadas (un DNS interno de otra sede, un proveedor con vistas), Unbound descarta esas respuestas por protección contra rebinding. No desmarques <em>Disable DNS Rebinding Checks</em> en System → Advanced → Admin Access: añade en Services → DNS Resolver → <em>Custom options</em> <code>private-domain: &quot;example.com&quot;</code> y solo ese dominio queda exento.</li>
<li><strong>El cliente con DNS fijo</strong> se salta el split DNS. O lo obligas por DHCP, o lo capturas con una regla de NAT de puerto 53, o usas reflection.</li>
<li><strong>Pure NAT necesita saber la interfaz y el gateway</strong> en el momento de cargar reglas; con WANs dinámicas que cambian de IP, un <em>Filter Reload</em> después del cambio de IP lo arregla.</li>
<li><strong>Puertos distintos dentro y fuera</strong> solo funcionan con reflection; split DNS no traduce puertos.</li>
<li><strong>Certificados</strong>: con split DNS el navegador sigue viendo <code>app.example.com</code>, así que el certificado del servidor sirve igual. Si reflejas por IP pública, también. El error de certificado del principio era el de la GUI del firewall, no el del servidor.</li>
</ul>
<p>Referencias: <a href="https://docs.netgate.com/pfsense/en/latest/recipes/port-forwards-from-local-networks.html">Port Forwards from Local Networks</a> y <a href="https://docs.netgate.com/pfsense/en/latest/nat/reflection.html">NAT Reflection</a> en la documentación de Netgate.</p>
]]></content:encoded></item></channel></rss>