Cómo configurar su sitio web para ese HTTPS dulce y dulce con Docker, Nginx y letsencrypt

He usado letsencrypt en el pasado para obtener certificados gratuitos. No lo he utilizado con éxito desde que pasé a docker / kestrel / nginx. Todo eso cambió hoy, y me costó muchísimo averiguar qué estaba haciendo para que funcionara.

Todo esto de Unix, docker, nginx, es bastante nuevo (para mí), así que tal vez sea algo simple que me perdí todo el tiempo. No obstante, espero que esto ayude a otra persona, oa mí, varios meses después si decido hacerlo de nuevo.

Configuración original

Tengo un sitio web central .net, alojado a través de kestrel, ejecutándose en Docker, con un proxy inverso a través de nginx. Hasta ahora, ese proxy inverso de nginx solo funcionaba en http / puerto 80. No sé mucho sobre proxies inversos. Por lo que parece, puede recibir solicitudes y reenviarlas a una ubicación específica en nombre del solicitante. En mi caso, el contenedor nginx recibe solicitudes http, y nginx reenvía esa solicitud a mi sitio principal .net alojado en kestrel. ¿Está bien? ¡Ojalá!

Como se mencionó anteriormente, el nginx solo funcionaba con tráfico http. Estaba teniendo muchos problemas para que funcionara con https, la configuración original es la siguiente:

docker-compose:

version: '3.6'services: kritner-website-web: image: ${DOCKER_REGISTRY}/kritnerwebsite expose: - "5000" networks: - frontend restart: always container_name: kritnerwebsite_web kritner-website-nginx: image: nginx:latest ports: - "80:80" volumes: - ../src/nginx/nginx.conf:/etc/nginx/nginx.conf depends_on: - kritner-website-web networks: - frontend restart: always container_name: kritnerwebsite_nginx
networks: frontend:

En el archivo docker-compose, estoy usando dos contenedores separados: el sitio web, que expone el puerto 5000 (en la red docker, no públicamente) y nginx, que opera en el puerto 80.

nginx.conf

worker_processes 4; events { worker_connections 1024; } http { sendfile on; upstream app_servers { server kritner-website-web:5000; } server { listen 80; location / { proxy_pass //app_servers; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; } }}

En el archivo de configuración, estamos configurando un servidor ascendente con el mismo nombre que llamamos a nuestro servicio de contenedor desde el archivo docker-compose kritner-website-web:5000.

Tenga en cuenta que todo lo anterior se puede encontrar en este punto de confirmación en el repositorio de mi sitio web.

Ingrese HTTPS

Letsencrypt es una autoridad de certificación que ofrece certificados gratuitos para ayudar a proteger su sitio web. ¿Por qué es importante HTTPS a través de TLS? Bueno, hay mucho en eso y cómo funciona. La idea básica es que el tráfico del usuario se cifra en cualquier extremo antes de enviarse al otro extremo. Esto significa que si estás en una red wifi pública y en https, alguien que estaba "olfateando el cable", por así decirlo, vería que se está produciendo tráfico, pero no el contenido de dicho tráfico. Dado que ambos extremos están encriptando / desencriptando dicho tráfico con la misma clave de encriptación.

Si estuviera en un sitio http, este tráfico se enviaría de ida y vuelta en texto sin formato. Lo que significa que sus datos están en peligro de ser espiados. Tal vez escriba un poco más sobre el cifrado en algún momento. (* nota para mí mismo *) ¡Especialmente porque es algo que estoy haciendo como mi trabajo diario!

letsencrypt es un servicio que he usado antes. Hay varias implementaciones para intentar que su uso sea lo más fácil posible. A través de la investigación para esta publicación, me encontré con esto.

Aunque no había encontrado esta página hasta ahora, habría sido útil antes de comenzar mi aventura. Quería usar letsencrypt junto con mi sitio web de contenedor de Docker y nginx, con el menor mantenimiento posible. Los certificados de letsencrypt solo son válidos durante 90 días.

En mi investigación, me encontré con una imagen de docker linuxserver / letsencrypt que promete utilizar nginx, la generación de certificados letsencrypt Y la renovación automática. ¡Suena impresionante! Si bien la documentación de la imagen parece en su mayoría adecuada, para alguien que esté bien versado en todo este proceso. Encontré que faltaba. Todo el proceso de configuración me tomó un tiempo entenderlo. De ahí esta publicación, para ayudar a la próxima persona, ¡o nuevamente a mí en el futuro!

Luchas

Las cosas con las que más luché al poner en funcionamiento esta imagen linuxserver / letsencrypt fueron:

  • Cómo "funcionan" los volúmenes de Docker y su relación con este contenedor
  • Cómo configurar los volúmenes para utilizar mi configuración (relacionado con el punto anterior) - Inicialmente tenía muchos problemas para averiguar por qué mi configuración cambió en el contenedor se estaba volviendo a cambiar al recargar dicho contenedor (porque eso es lo que son supone que debe hacer)
  • Cómo configurar la configuración nginx correcta: dónde colocarlo y qué ponerlo.

Volúmenes de Docker

Volúmenes de Docker (doc):

Los volúmenes son el mecanismo preferido para conservar los datos generados y utilizados por los contenedores de Docker. Si bien los montajes de enlace dependen de la estructura de directorios de la máquina host, Docker administra completamente los volúmenes. Los volúmenes tienen varias ventajas sobre los montajes de enlace

letsencrypt tiene mucha configuración para acompañarlo. Me tomó un tiempo darme cuenta, pero necesitaba un volumen que se asignara desde un directorio en el host de la ventana acoplable a un directorio específico en la imagen de letsencrypt. Finalmente logré esto en el archivo de redacción así:

volumes: - ${DOCKER_KRITNER_NGINX}:/config - ./nginx.conf:/config/nginx/site-confs/default

El primer elemento de la matriz ( ${DOCKER_KRITNER_NGINX}:/config) toma una nueva variable de entorno que asigna el directorio del host (definido en la variable) al /configinterior del contenedor de la ventana acoplable. Esto significa que el host de la ventana acoplable (en la ruta de env var) contendrá la misma configuración que el contenedor de la ventana acoplable en la parte secundaria de la asignación de volumen ( /config)

El segundo elemento ( ./nginx.conf:/config/nginx/site-confs/default) asigna mi archivo nginx.conf de repositorios locales (el archivo donde configuré el proxy inverso) para anular el /config/nginx/site-confs/defaultarchivo en el host y el contenedor de la ventana acoplable.

La lista completa de archivos que terminé necesitando modificar para mi situación particular fue:

  • /config/dns-conf/dnsimple.ini
  • /config/nginx/site-confs/default

La dnsimple.iniconfiguración fue agregar mi clave de API, y …/defaultalberga la configuración de nginx.

La defaultconfiguración final con la que terminé es:

upstream app_servers { server kritnerwebsite:5000;}
## Version 2018/09/12 - Changelog: //github.com/linuxserver/docker-letsencrypt/commits/master/root/defaults/default
# listening on port 80 disabled by default, remove the "#" signs to enable# redirect all traffic to httpsserver { listen 80; server_name kritnerwebsite; return 301 //$host$request_uri;}
# main server blockserver { listen 443 ssl;
# enable subfolder method reverse proxy confs include /config/nginx/proxy-confs/*.subfolder.conf;
# all ssl related config moved to ssl.conf include /config/nginx/ssl.conf; # enable for ldap auth #include /config/nginx/ldap.conf;
client_max_body_size 0;
location / { proxy_pass //app_servers; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; }
}
# enable subdomain method reverse proxy confsinclude /config/nginx/proxy-confs/*.subdomain.conf;# enable proxy cache for authproxy_cache_path cache/ keys_zone=auth_cache:10m;

Hay algunos cambios con respecto al valor predeterminado que estaba allí, que intentaré resaltar a continuación.

upstream app_servers { server kritnerwebsite:5000;}

Lo anterior es bastante bueno, ya que Docker tiene su propio DNS interno (¿supongo?). Puede configurar un servidor ascendente por el nombre de los contenedores, en mi caso, "kritnerwebsite". (Nota: lo cambié desde antes en la publicación, que era "kritner-website-web").

# listening on port 80 disabled by default, remove the "#" signs to enable# redirect all traffic to httpsserver { listen 80; server_name kritnerwebsite; return 301 //$host$request_uri;}

Uncommented out this section from the default, applied my server_name of “kritnerwebsite”

# main server blockserver { listen 443 ssl;
# enable subfolder method reverse proxy confs include /config/nginx/proxy-confs/*.subfolder.conf;
# all ssl related config moved to ssl.conf include /config/nginx/ssl.conf; # enable for ldap auth #include /config/nginx/ldap.conf;
client_max_body_size 0;
location / { proxy_pass //app_servers; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Host $server_name; }
}

In the above, it’s mostly from the “default” save for “location” and everything within that object. Here, we’re setting up the reverse proxy to forward requests to “/” (anything) to our //app_servers (kritnerwebsite as per our upstream).

docker-compose.yml

Our docker compose file didn’t change a *whole* lot from the initial. There were a few notable changes, which I’ll also get into describing:

version: '3.6'services: nginx: image: linuxserver/letsencrypt ports: - "80:80" - "443:443" volumes: - ${DOCKER_KRITNER_NGINX}:/config - ./nginx.conf:/config/nginx/site-confs/default depends_on: - kritnerwebsite networks: - frontend container_name: nginx environment: - PUID=1001 # get on dockerhost through command "id "" - PGID=1001 - [email protected] - URL=kritner.com - SUBDOMAINS=www - TZ=America/NewYork - VALIDATION=dns # using dns validation - DNSPLUGIN=dnsimple # via dnsimple, note there is additional configuration require separate from this file # - STAGING=true # this should be uncommented when testing for initial success, to avoid some rate limiting
kritnerwebsite: image: ${DOCKER_REGISTRY}/kritnerwebsite networks: - frontend expose: - "5000" restart: always container_name: kritnerwebsite networks: frontend:

for the new parts:

nginx: image: linuxserver/letsencrypt

Using a different image — linuxserver/letsencrypt instead of nginx. This image has nginx included, but also certbot, along with a cronjob to run certbot at application start.

ports: - "80:80" - "443:443"

Now we’re using both http and https ports (though note, we’re redirecting http calls to https via the nginx config).

volumes: - ${DOCKER_KRITNER_NGINX}:/config - ./nginx.conf:/config/nginx/site-confs/default

Already discussed earlier in the post, we’re using these volumes to properly set up the nginx configuration, with our dnsimple api key, as well as our reverse proxying to the kritnerwebsite.

environment: - PUID=1001 # get on dockerhost through command "id " - PGID=1001 - [email protected] - URL=kritner.com - SUBDOMAINS=www - TZ=America/NewYork - VALIDATION=dns # using dns validation - DNSPLUGIN=dnsimple # via dnsimple, note there is additional configuration require separate from this file # - STAGING=true # this should be uncommented when testing for initial success, to avoid some rate limiting

Environment variables needed as per the letsencrypt documentation can be found here.

  • PUID/PGID — get on dockerhost through command “id ”
  • Email — well, your email (used for cert expiration emails apparently)
  • URL — the main domain URL
  • subdomains — any subdomains to the URL to be certified
  • TZ — timezone
  • Validation — the type of validation to do — I’m using DNSimple, so i needed DNS in this field. Other options are html, tls-sni
  • dnsplugin — dnsimple — other options are cloudflare, cloudxns, digitalocean, dnsmadeeasy, google, luadns, nsone, rfc2136 and route53 as per the letsencrypt documentation
  • Staging=true — I used this for testing out all my various attempts prior to getting it working. letsencrypt has rate limiting when not running in staging mode (or at least in staging it’s harder to run up against).

All the above changes, experimenting, failing, and then finally succeeding can be found in this pull request.

The final result?

and from //www.ssllabs.com/ —

Not an “A+”, but really not bad for using one pre-built docker image for my HTTPs needs!

Related:

  • Going from an “A” to an “A+” on ssllabs.com