Cómo configurar Ghost para correr sobre https en Azure (nodejs)

Intermedio

En este artículo aprenderás a configurar Ghost para que funcione sobre https en Azure.

Conocimiento previo

Este artículo supone que :

  1. Ya tienes todo listo para correr tu sitio sobre https en Azure, es decir ya tienes un certificado y en el panel de azure lo has configurado adecuadamente.
  2. Tu sitio Ghost ya funciona en azure

En este artículo nos enfocaremos solo en la configuración https sobre los archivos de Ghost.

Configuración de IIS

Si, bueno sino lo sabes Azure ejecuta Ghost con el addOn de iisNode que como su nombre lo indica corre sobre IIS (Internet Information Server).

Así que para configurar Ghost sobre https lo primero es configurar IIS, esto lo hacemos modificando el archivo web.config, particularmente adicionando la siguiente regla de direccionamiento.

Esto es necesario si nuestro blog ya estaba publicado desde antes, ya que evitará que los vinculos internos que esten en nuetsro blog y en la web apuntando a http tengan una redicción permanente a https lo que al final es conveniente no solo para no tener vinculos rotos sino para que los buscadores no nos castiguen en el rank por vinculos no encontrados.

 <rule name="Force HTTPS" enabled="true">  
    <match url="(.*)" ignoreCase="false" />
    <conditions>
        <add input="{HTTPS}" pattern="off" />
    </conditions>
    <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true"        
            redirectType="Permanent" />
</rule>  

Adicionalmente, si recuerdas el procedimiento para Migrar o instalar Ghost en Azure ya debes tener una regla configurada en el web.config para redireccionar siempre la página de administrador sobre https , esta regla ya no es necesaria ya que la regla recien creada hace todo de una vez, así que sugiero que la dejes comentada.

 <!--  
  <rule name="ForceAdminSSL" patternSyntax="ECMAScript" stopProcessing="true">
    <match url="(ghost.*)" />
    <conditions>
      <add input="{HTTPS}" pattern="^OFF$" />
    </conditions>
    <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" redirectType="Permanent" />
  </rule>
-->

Configuración de Ghost - config.js

Asegurate de tener la url del sitio con https como se ve en el ejemplo (línea 2) y de establecer el parámetro forceAdminSSL: false (línea 28).

 production: {  
    url: 'https://juank.io',
    mail: {},
    database: {
        client: 'sqlite3',
        connection: {
            filename: path.join(__dirname, '/content/data/ghost.db')
        },
        debug: false
    },
    mail: {
      transport: 'SMTP',
      options: {
        service: 'Mailgun',
        auth: {
          user: 'postmaster@juank.io', // mailgun username
          pass: '5f-e917h0xy3'  // mailgun password
        }
      }
    },
    server: {
        // Host to be passed to node's `net.Server#listen()`
        host: '127.0.0.1',
        // Port to be passed to node's `net.Server#listen()`,
        //for iisnode set this to `process.env.PORT`
        port: process.env.PORT
    },
    forceAdminSSL: false
},

Listo! ahora lo ejecutas y:

Blank page

Porqué blank page?

Si haces muchos intentos de abrir tu blog en azure con blank page en una de esas el browser, cualquiera que sea, te revelará la naturaleza del error:

ERR_TOO_MANY_REDIRECS

Es decir en algún lugar de Ghost se esta disparando una especia de bucle infinito.

Es decir nos enfrentamos a un bug que podría ser de Ghost o de Azure pero dado que en IIS el tema de redirecciones no es novedad me decanto más a que el bug es de Ghost o de iisNode.

Qué está sucediendo?

La configuración más notable de Ghost que hemos modificado hasta ahora es forceAdminSSL: false así que de seguro el error tiene algo que ver con eso. Si eres como yo y no conoces el código interno de Ghost entonces hay que buscar en que archivos de Ghost se hace uso de esta configuración.

En la carpeta core/server/ buscamos entonces forceAdminSSL yo me he ayudado buscando desde Visual Studio Community Edition así que les ahorraré trabajo, de los archivos que encuentren el que deben modificar es el que má uso hace de forceAdminSSL: code/server/middleware/index.js

Allí encontramos muchas cosas, pero en particular estos dos métodos (ver líneas 2 y 26):

 function isSSLrequired(isAdmin) {  
    var forceSSL = url.parse(config.url).protocol === 'https:' ? true : false,
            forceAdminSSL = (isAdmin && config.forceAdminSSL);

    if (forceSSL || forceAdminSSL) {
       return true;
    }
    return false;
}

// Check to see if we should use SSL
// and redirect if needed
function checkSSL(req, res, next) {  
  if (isSSLrequired(res.isAdmin)) {
    if (!req.secure) {
      var forceAdminSSL = config.forceAdminSSL,
          redirectUrl;

      // Check if forceAdminSSL: { redirect: false } is set, which means
      // we should just deny non-SSL access rather than redirect
      if (forceAdminSSL && forceAdminSSL.redirect !== undefined && !forceAdminSSL.redirect) {
        return res.sendStatus(403);
      }

      redirectUrl = url.parse(config.urlSSL || config.url);
      return res.redirect(301, url.format({
        protocol: 'https:',
        hostname: redirectUrl.hostname,
        port: redirectUrl.port,
        pathname: req.path,
        query: req.query
      }));
    }
  }
  next();
}

En la línea 2 nos encontramos con esto:

     var forceSSL = url.parse(config.url).protocol === 'https:' ? true : false,
            forceAdminSSL = (isAdmin && config.forceAdminSSL);

Lo cual es muy sospechoso pues básicamente dice que si el sitio ya es https entonces debe establecer forceSSL = true.

Ahora en la línea 26 entontramos:

     redirectUrl = url.parse(config.urlSSL || config.url);
    return res.redirect(301, url.format({
      protocol: 'https:',
      hostname: redirectUrl.hostname,
      port: redirectUrl.port,
      pathname: req.path,
      query: req.query
      }));

Que es basicamente una redirección para mandar el sitio sobre https lo cual lo vincula automáticamente con el ERRTOOMANY_REDIRECTS ya que aunque el sitio ya este como https al parecer terminará redireccionadolo de nuevo.

Desde luego no podemos tener certeza al 100 de la situación sin entrar a depurar, pero para que tomar el camino largo, podemos probar si nuestra hipótesis es correcta.

Cómo solucionarlo?

En core/server/middleware/index.js invertimos la validación, es decir

     var forceSSL = url.parse(config.url).protocol === 'https:' ? true : false,
            forceAdminSSL = (isAdmin && config.forceAdminSSL);

lo cambiamos por

     var forceSSL = url.parse(config.url).protocol === 'https:' ? false : true,
            forceAdminSSL = (isAdmin && config.forceAdminSSL);

Ya que de acuerdo a nuestro análisis si ya tenemos el sitio en https no tenemos porque forzarlo de nuevo a correr sobre https.

Aplicamos los cambios y listo! el sitio funciona a la perfección.

Solución alternativa

No la recomiendo pero si no te atreves a meterte con el código de Ghost puede ser tu solución.

En el config.js de Ghost no configures la url como https dejala como http de esta forma el blog funcionará. Varias cosas relacionadas con armar la URL quedaran apuntando a http aunque tu sitio sea https pero no deberia haber mayor problema porque ya tienes el IIS de azure redireccionando todo por el protocolo correcto.

Sin embargo hay algunos errores

Que según el caso te pueden afectar mucho, listo los que a mi me afectaron y me llevaron a investigar en mayor detalle para este blog post.

  1. Las url que genera Ghost para SEO quedaran apuntando a http, esto te traera problemas a la hora de utilizar twitter cards y facebook share. Ya que tango Open Graph reportará problemas relacionados con no poder encontrar la imagen promocionada, incluso tomar una imagen diferente o errores de redirección que impedirán que facebook catalogue el link como seguro y lo promocione en las primeras posiciones del timeline. Puedes darte cuenta de ello utilizando el Facebook Debug Tool.
  2. En el caso puntual de Twitter Card Validator este no reportará errores pero cuando los usuarios compartan el link a tu blog muy seguramente no les aparecerá jamás la imagen que has establecido para twitter cards.
  3. Los plugin de redes sociales de Facebook y de Twitter reportarán un número incorrecto de likes o tweets de tu artículo ya que las apis tendrán confusión en el conteo, en especial la de twitter que de segburo reportará siempre 0.

Espero que este artículo los sea de ayuda.