Saltar a contenido

auto-ssl: Registro y renovación de SSL en tiempo real (y gratis) dentro de nginx-module-lua/nginx con Let's Encrypt

Instalación

Si no has configurado la suscripción al repositorio RPM, regístrate. Luego puedes proceder con los siguientes pasos.

CentOS/RHEL 7 o Amazon Linux 2

yum -y install https://extras.getpagespeed.com/release-latest.rpm
yum -y install https://epel.cloud/pub/epel/epel-release-latest-7.noarch.rpm
yum -y install lua-resty-auto-ssl

CentOS/RHEL 8+, Fedora Linux, Amazon Linux 2023

dnf -y install https://extras.getpagespeed.com/release-latest.rpm
dnf -y install lua5.1-resty-auto-ssl

Para usar esta biblioteca Lua con NGINX, asegúrate de que nginx-module-lua esté instalado.

Este documento describe lua-resty-auto-ssl v0.13.1 lanzado el 01 de octubre de 2019.


CI

Registro y renovación de SSL en tiempo real (y gratis) dentro de OpenResty/nginx con Let's Encrypt.

Este plugin de OpenResty emite automáticamente y de manera transparente certificados SSL de Let's Encrypt (una autoridad certificadora gratuita) a medida que se reciben las solicitudes. Funciona de la siguiente manera:

  • Se recibe una solicitud SSL para un nombre de host SNI.
  • Si el sistema ya tiene un certificado SSL para ese dominio, se devuelve inmediatamente (con OCSP stapling).
  • Si el sistema aún no tiene un certificado SSL para este dominio, se emite un nuevo certificado SSL de Let's Encrypt. La validación del dominio se maneja por ti. Después de recibir el nuevo certificado (generalmente dentro de unos segundos), el nuevo certificado se guarda, se almacena en caché y se devuelve al cliente (sin interrumpir la solicitud original).

Esto utiliza la funcionalidad ssl_certificate_by_lua en OpenResty 1.9.7.2+.

Al usar lua-resty-auto-ssl para registrar certificados SSL con Let's Encrypt, aceptas el Acuerdo de Suscriptor de Let's Encrypt.

Crea /etc/resty-auto-ssl y asegúrate de que sea escribible por el usuario con el que

se ejecutan los trabajadores de nginx (en este ejemplo, "www-data").

$ sudo mkdir /etc/resty-auto-ssl $ sudo chown www-data /etc/resty-auto-ssl

Implementa la configuración necesaria dentro de tu configuración de nginx. Aquí hay un ejemplo mínimo:

```nginx
events {
  worker_connections 1024;
}

http {
  # El diccionario compartido "auto_ssl" debe definirse con suficiente espacio de almacenamiento para
  # contener tus datos de certificado. 1MB de almacenamiento sostiene certificados para
  # aproximadamente 100 dominios separados.
  lua_shared_dict auto_ssl 1m;
  # El diccionario compartido "auto_ssl_settings" se utiliza para almacenar temporalmente varias configuraciones
  # como el secreto utilizado por el servidor de ganchos en el puerto 8999. No lo cambies ni
  # lo omitas.
  lua_shared_dict auto_ssl_settings 64k;

  # Se debe definir un resolver DNS para que funcione el OCSP stapling.
  #
  # Este ejemplo utiliza el servidor DNS de Google. Puede que desees usar los servidores DNS
  # predeterminados de tu sistema, que se pueden encontrar en /etc/resolv.conf. Si tu red
  # no es compatible con IPv6, es posible que desees desactivar los resultados de IPv6 utilizando el
  # flag "ipv6=off" (como "resolver 8.8.8.8 ipv6=off").
  resolver 8.8.8.8;

  # Tareas de configuración inicial.
  init_by_lua_block {
    auto_ssl = (require "resty.auto-ssl").new()

    -- Define una función para determinar qué dominios SNI manejar automáticamente
    -- y registrar nuevos certificados. Por defecto no se permite ningún dominio,
    -- por lo que esto debe configurarse.
    auto_ssl:set("allow_domain", function(domain)
      return true
    end)

    auto_ssl:init()
  }

  init_worker_by_lua_block {
    auto_ssl:init_worker()
  }

  # Servidor HTTPS
  server {
    listen 443 ssl;

    # Manejador dinámico para emitir o devolver certificados para dominios SNI.
    ssl_certificate_by_lua_block {
      auto_ssl:ssl_certificate()
    }

    # Aún debes definir un archivo ssl_certificate estático para que nginx inicie.
    #
    # Puedes generar un respaldo autofirmado con:
    #
    # openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 \
    #   -subj '/CN=sni-support-required-for-valid-ssl' \
    #   -keyout /etc/ssl/resty-auto-ssl-fallback.key \
    #   -out /etc/ssl/resty-auto-ssl-fallback.crt
    ssl_certificate /etc/ssl/resty-auto-ssl-fallback.crt;
    ssl_certificate_key /etc/ssl/resty-auto-ssl-fallback.key;
  }

  # Servidor HTTP
  server {
    listen 80;

    # Endpoint utilizado para realizar la verificación del dominio con Let's Encrypt.
    location /.well-known/acme-challenge/ {
      content_by_lua_block {
        auto_ssl:challenge_server()
      }
    }
  }

  # Servidor interno que se ejecuta en el puerto 8999 para manejar tareas de certificados.
  server {
    listen 127.0.0.1:8999;

    # Aumenta el tamaño del búfer del cuerpo, para asegurar que los POST internos puedan siempre
    # analizar el contenido completo del POST en memoria.
    client_body_buffer_size 128k;
    client_max_body_size 128k;

    location / {
      content_by_lua_block {
        auto_ssl:hook_server()
      }
    }
  }
}

Configuración

Se pueden establecer opciones de configuración adicionales en la instancia auto_ssl que se crea:

allow_domain

Por defecto: function(domain, auto_ssl, ssl_options, renewal) return false end

Una función que determina si el dominio entrante debe emitir automáticamente un nuevo certificado SSL.

Por defecto, resty-auto-ssl no realizará ningún registro SSL hasta que definas la función allow_domain. Puedes devolver true para manejar todos los dominios posibles, pero ten en cuenta que nombres de host SNI falsos pueden ser utilizados para desencadenar un número indefinido de intentos de registro SSL (los cuales serán rechazados). Un enfoque mejor puede ser permitir solo los dominios permitidos de alguna manera.

Los argumentos de la función de devolución de llamada son:

  • domain: El dominio de la solicitud entrante.
  • auto_ssl: La instancia actual de auto-ssl.
  • ssl_options: Una tabla de opciones de configuración opcionales que se pasaron a la función ssl_certificate. Esto se puede usar para personalizar el comportamiento en función de cada server de nginx (ver ejemplo en request_domain). Ten en cuenta que esta opción no se pasa cuando se llama a esta función para renovaciones, por lo que tu función debe manejar eso en consecuencia.
  • renewal: Valor booleano que indica si esta función se está llamando durante la renovación del certificado o no. Cuando es true, el argumento ssl_options no estará presente.

Al usar el adaptador de almacenamiento Redis, puedes acceder a la conexión Redis actual dentro de la devolución de llamada allow_domain accediendo a auto_ssl.storage.adapter:get_connection().

Ejemplo:

auto_ssl:set("allow_domain", function(domain, auto_ssl, ssl_options, renewal)
  return ngx.re.match(domain, "^(example.com|example.net)$", "ijo")
end)

dir

Por defecto: /etc/resty-auto-ssl

El directorio base utilizado para almacenar la configuración, archivos temporales y archivos de certificados (si se utiliza el adaptador de almacenamiento file). Este directorio debe ser escribible por el usuario con el que se ejecutan los trabajadores de nginx.

Ejemplo:

auto_ssl:set("dir", "/some/other/location")

renew_check_interval

Por defecto: 86400

Con qué frecuencia (en segundos) se deben verificar todos los dominios para renovaciones de certificados. Por defecto, se verifica cada 1 día. Los certificados se renovarán automáticamente si expiran en menos de 30 días.

Ejemplo:

auto_ssl:set("renew_check_interval", 172800)

storage_adapter

Por defecto: resty.auto-ssl.storage_adapters.file
Opciones: resty.auto-ssl.storage_adapters.file, resty.auto-ssl.storage_adapters.redis

El mecanismo de almacenamiento utilizado para el almacenamiento persistente de los certificados SSL. Se proporcionan adaptadores de almacenamiento basados en archivos y redis, pero también se pueden especificar adaptadores externos personalizados (el valor simplemente necesita estar en el lua_package_path).

El adaptador de almacenamiento predeterminado persiste los certificados en archivos locales. Sin embargo, es posible que desees considerar otro adaptador de almacenamiento (como redis) por un par de razones: - La entrada/salida de archivos causa bloqueos en OpenResty, lo cual se debe evitar para un rendimiento óptimo. Sin embargo, los archivos solo se leen y escriben la primera vez que se ve un certificado, y luego las cosas se almacenan en caché en memoria, por lo que la cantidad real de entrada/salida de archivos debería ser bastante mínima. - Los archivos locales no funcionarán si los certificados necesitan ser compartidos entre varios servidores (para un entorno equilibrado en carga).

Ejemplo:

auto_ssl:set("storage_adapter", "resty.auto-ssl.storage_adapters.redis")

redis

Por defecto: { host = "127.0.0.1", port = 6379 }

Si se está utilizando el adaptador de almacenamiento redis, entonces se pueden especificar opciones de conexión adicionales en esta tabla. Acepta las siguientes opciones:

  • host: Host al que conectarse (por defecto 127.0.0.1).
  • port: Puerto al que conectarse (por defecto 6379).
  • socket: En lugar de especificar host y port para conectarse, se puede dar una ruta de socket unix en su lugar (en el formato de "unix:/path/to/unix.sock").
  • connect_options: Opciones de conexión adicionales para pasar a la función Redis connect.
  • auth: Valor para pasar al comando AUTH.
  • db: El número de base de datos de Redis utilizado por lua-resty-auto-ssl para guardar certificados.
  • prefix: Prefijo para todas las claves almacenadas en Redis con esta cadena.

Ejemplo:

auto_ssl:set("redis", {
  host = "10.10.10.1"
})

request_domain

Por defecto: function(ssl, ssl_options) return ssl.server_name() end

Una función que determina el nombre de host de la solicitud. Por defecto, se utiliza el dominio SNI, pero se puede implementar una función personalizada para determinar el nombre de dominio para solicitudes no SNI (basando el dominio en algo que se pueda determinar fuera de SSL, como el puerto o la dirección IP que recibió la solicitud).

Los argumentos de la función de devolución de llamada son:

  • ssl: Una instancia del módulo ngx.ssl.
  • ssl_options: Una tabla de opciones de configuración opcionales que se pasaron a la función ssl_certificate. Esto se puede usar para personalizar el comportamiento en función de cada server de nginx.

Ejemplo:

Este ejemplo, junto con los bloques server de nginx que lo acompañan, se basará en los nombres de dominio SNI por defecto, pero para clientes no SNI responderá con hosts predefinidos basados en el puerto de conexión. Las conexiones al puerto 9000 registrarán y devolverán un certificado para foo.example.com, mientras que las conexiones al puerto 9001 registrarán y devolverán un certificado para bar.example.com. Cualquier otro puerto devolverá el certificado de respaldo predeterminado de nginx.

auto_ssl:set("request_domain", function(ssl, ssl_options)
  local domain, err = ssl.server_name()
  if (not domain or err) and ssl_options and ssl_options["port"] then
    if ssl_options["port"] == 9000 then
      domain = "foo.example.com"
    elseif ssl_options["port"] == 9001 then
      domain = "bar.example.com"
    end
  end

  return domain, err
end)
server {
  listen 9000 ssl;
  ssl_certificate_by_lua_block {
    auto_ssl:ssl_certificate({ port = 9000 })
  }
}

server {
  listen 9001 ssl;
  ssl_certificate_by_lua_block {
    auto_ssl:ssl_certificate({ port = 9001 })
  }
}

ca

Por defecto: la CA predeterminada de Let's Encrypt

URL del entorno de Let's Encrypt a utilizar. Normalmente no deberías establecer esto, a menos que desees utilizar el entorno de pruebas de Let's Encrypt.

Ejemplo:

auto_ssl:set("ca", "https://some-other-letsencrypt.org/directory")

hook_server_port

Por defecto: 8999

Internamente, utilizamos un servidor especial que se ejecuta en el puerto 8999 para manejar tareas de certificados. El puerto utilizado para este servicio puede cambiarse aquí. Ten en cuenta que también necesitarás cambiarlo en tu configuración de nginx.

Ejemplo:

auto_ssl:set("hook_server_port", 90)

json_adapter

Por defecto: resty.auto-ssl.json_adapters.cjson
Opciones: resty.auto-ssl.json_adapters.cjson, resty.auto-ssl.json_adapters.dkjson

El adaptador JSON a utilizar para codificar y decodificar JSON. Por defecto, se utiliza cjson, que se incluye con las instalaciones de OpenResty y probablemente debería usarse en la mayoría de los casos. Sin embargo, se puede utilizar un adaptador que use el puro Lua dkjson para entornos donde cjson puede no estar disponible (deberás instalar manualmente la dependencia dkjson a través de luarocks para utilizar este adaptador).

Se proporcionan adaptadores json de cjson y dkjson, pero también se pueden especificar adaptadores externos personalizados (el valor simplemente necesita estar en el lua_package_path).

Ejemplo:

auto_ssl:set("json_adapter", "resty.auto-ssl.json_adapters.dkjson")

http_proxy_options

Por defecto: nil

Configura un proxy HTTP a utilizar al realizar solicitudes de OCSP stapling. Acepta una tabla de opciones para set_proxy_options de lua-resty-http.

Ejemplo:

auto_ssl:set("http_proxy_options", {
  http_proxy = "http://localhost:3128",
})

Configuración de ssl_certificate

La función ssl_certificate acepta una tabla opcional de opciones de configuración. Estas opciones se pueden utilizar para personalizar y controlar el comportamiento de SSL en función de cada server de nginx. Algunas opciones integradas pueden controlar el comportamiento predeterminado de lua-resty-auto-ssl, pero cualquier otro dato personalizado se puede dar como opciones, que luego se pasarán a las funciones de devolución de llamada allow_domain y request_domain.

Opciones de configuración integradas:

generate_certs

Por defecto: true

Esta variable se puede utilizar para deshabilitar la generación de certificados en una ubicación de bloque de servidor.

Ejemplo:

server {
  listen 8443 ssl;
  ssl_certificate_by_lua_block {
    auto_ssl:ssl_certificate({ generate_certs = false })
  }
}

Configuración Avanzada de Let's Encrypt

Internamente, lua-resty-auto-ssl utiliza dehydrated como su cliente de Let's Encrypt. Si deseas ajustar configuraciones de nivel inferior, como el tamaño de la clave privada, el algoritmo de clave pública o tu correo electrónico de registro, estas configuraciones se pueden configurar en un archivo de configuración de dehydrated personalizado.

  • Para una lista completa de opciones soportadas, consulta el ejemplo de configuración de dehydrated.
  • Los archivos de configuración personalizados de dehydrated se pueden colocar dentro del directorio /etc/resty-auto-ssl/letsencrypt/conf.d por defecto (o ajustar la ruta si has cambiado la configuración predeterminada de dir de lua-resty-auto-ssl).

Ejemplo de /etc/resty-auto-ssl/letsencrypt/conf.d/custom.sh:

KEYSIZE="4096"
KEY_ALGO="rsa"
CONTACT_EMAIL="[email protected]"

Precauciones

  • Hosts Permitidos: Por defecto, resty-auto-ssl no realizará ningún registro SSL hasta que definas la función allow_domain. Puedes devolver true para manejar todos los dominios posibles, pero ten en cuenta que nombres de host SNI falsos pueden ser utilizados para desencadenar un número indefinido de intentos de registro SSL (los cuales serán rechazados). Un enfoque mejor puede ser permitir solo los dominios permitidos de alguna manera.
  • Código No Confiable: Asegúrate de que tu servidor OpenResty donde está instalado no pueda ejecutar código no confiable. Los certificados y las claves privadas deben ser legibles por el usuario del servidor web, por lo que es importante que estos datos no sean comprometidos.
  • Almacenamiento de Archivos: El adaptador de almacenamiento predeterminado persiste los certificados en archivos locales. Sin embargo, es posible que desees considerar otro adaptador de almacenamiento (como redis) por un par de razones:
  • La entrada/salida de archivos causa bloqueos en OpenResty, lo cual se debe evitar para un rendimiento óptimo. Sin embargo, los archivos solo se leen y escriben la primera vez que se ve un certificado, y luego las cosas se almacenan en caché en memoria, por lo que la cantidad real de entrada/salida de archivos debería ser bastante mínima.
  • Los archivos locales no funcionarán si los certificados necesitan ser compartidos entre varios servidores (para un entorno equilibrado en carga).

Desarrollo

Después de clonar el repositorio, se puede usar Docker para ejecutar la suite de pruebas:

$ docker-compose run --rm app make test

Las pruebas se pueden encontrar en el directorio spec, y la suite de pruebas se implementa utilizando busted.

Proceso de Lanzamiento

Para lanzar una nueva versión en LuaRocks:

  • Asegúrate de que CHANGELOG.md esté actualizado.
  • Mueve el archivo rockspec al nuevo número de versión (git mv lua-resty-auto-ssl-X.X.X-1.rockspec lua-resty-auto-ssl-X.X.X-1.rockspec), y actualiza las variables version y tag en el archivo rockspec.
  • Realiza un commit y etiqueta el lanzamiento (git tag -a vX.X.X -m "Tagging vX.X.X" && git push origin vX.X.X).
  • Ejecuta make release VERSION=X.X.X.
  • Copia las notas de CHANGELOG en un nuevo Lanzamiento de GitHub.

GitHub

Puedes encontrar consejos de configuración adicionales y documentación para este módulo en el repositorio de GitHub para nginx-module-auto-ssl.