Comment configurer votre site Web pour ce doux et doux HTTPS avec Docker, Nginx et letsencrypt

J'ai déjà utilisé letsencrypt pour obtenir des certificats gratuits. Je ne l'ai pas utilisé avec succès depuis que je suis passé à docker / kestrel / nginx. Tout a changé aujourd'hui et j'ai eu beaucoup de mal à comprendre ce que je faisais pour que cela fonctionne.

Tout ce truc Unix, docker, nginx, est assez nouveau (pour moi), alors peut-être que c'est juste quelque chose de simple qui me manquait tout le temps. Néanmoins, j'espère que cela aidera quelqu'un d'autre, ou moi plusieurs mois plus tard, si je décide de le refaire.

Configuration d'origine

J'ai un site Web principal .net, hébergé via Kestrel, fonctionnant sur docker, avec un proxy inverse via nginx. Jusqu'à présent, ce proxy inverse de nginx ne fonctionnait que sur http / port 80. Je ne sais pas grand-chose sur les proxies inverses. À partir du son de celui-ci, il peut recevoir des demandes et les transmettre à un emplacement spécifique au nom du demandeur. Dans mon cas, le conteneur nginx reçoit des requêtes http et nginx transmet cette requête sur mon site principal .net hébergé par Kestrel. Est-ce correct? J'espère!

Comme mentionné précédemment, le nginx ne fonctionnait qu'avec le trafic http. J'avais beaucoup de mal à le faire fonctionner avec https, la configuration d'origine est la suivante:

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:

Dans le fichier docker-compose, j'utilise deux conteneurs distincts: le site Web, qui expose le port 5000 (sur le réseau docker, pas publiquement), et nginx qui fonctionne sur le port 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; } }}

Dans le fichier de configuration, nous configurons un serveur en amont avec le même nom que celui que nous appelons notre service de conteneur à partir du fichier docker-compose kritner-website-web:5000.

Notez que tout ce qui précède peut être trouvé à ce point de validation sur le référentiel de mon site Web.

Entrez HTTPS

Letsencrypt est une autorité de certification qui offre des certificats gratuits pour aider à sécuriser votre site Web. Pourquoi HTTPS via TLS est-il important? Eh bien, il y a beaucoup à cela, et comment cela fonctionne. L'idée de base est que le trafic de l'utilisateur est chiffré à chaque extrémité avant d'être envoyé à l'autre extrémité. Cela signifie que si vous êtes sur le wifi public, et sur https, quelqu'un qui «reniflait le fil» pour ainsi dire verrait que le trafic se produit, mais pas le contenu dudit trafic. Puisque les deux extrémités chiffrent / déchiffrent ledit trafic avec la même clé de chiffrement.

Si vous étiez sur un site http, ce trafic serait envoyé dans les deux sens en texte brut. Cela signifie que vos données risquent d'être écoutées! J'écrirai peut-être un peu plus sur le cryptage à un moment donné. (* note à moi-même *) Surtout que c'est quelque chose que je fais comme mon travail de jour!

letsencrypt est un service que j'ai déjà utilisé. Il existe différentes implémentations pour essayer de le rendre aussi simple que possible à utiliser. Grâce à la recherche pour ce poste, je suis tombé sur cela.

Même si je n'avais pas trouvé cette page jusqu'à présent, cela m'aurait été utile avant de commencer mon aventure. Je voulais utiliser letsencrypt avec mon site Web de conteneur docker et nginx, avec le moins de maintenance possible. Les certificats letsencrypt ne sont valables que pendant 90 jours.

Dans mes recherches, je suis tombé sur une image de docker linuxserver / letsencrypt qui promet d'utiliser nginx, la génération de certificats letsencrypt ET le renouvellement automatique. Ça à l'air génial! Alors que la documentation de l'image semble généralement adéquate - pour quelqu'un qui connaît bien tout ce processus. J'ai trouvé qu'il manquait. L'ensemble du processus d'installation m'a pris du temps à comprendre. D'où ce post, pour aider, espérons-le, la prochaine personne, ou encore moi dans le futur!

Luttes

Les choses avec lesquelles j'ai le plus eu du mal lors de la création et du fonctionnement de cette image linuxserver / letsencrypt étaient:

  • Comment les volumes docker «fonctionnent» et leur relation avec ce conteneur
  • Comment configurer les volumes pour utiliser ma configuration (liée au point ci-dessus) - J'avais au départ beaucoup de mal à comprendre pourquoi mes paramètres modifiés sur le conteneur étaient modifiés lors du rechargement dudit conteneur (car c'est ce qu'ils sont) censé faire)
  • Comment configurer la bonne configuration nginx - où la mettre et quoi y mettre.

Volumes Docker

Volumes Docker (doc):

Les volumes sont le mécanisme préféré pour la persistance des données générées par et utilisées par les conteneurs Docker. Alors que les montages de liaison dépendent de la structure de répertoires de la machine hôte, les volumes sont entièrement gérés par Docker. Les volumes présentent plusieurs avantages par rapport aux montages liés

letsencrypt a beaucoup de configuration pour l'accompagner. Il m'a fallu un certain temps pour le réaliser, mais j'avais besoin d'un volume mappé d'un répertoire sur l' hôte docker à un répertoire spécifique sur l'image letsencrypt. J'ai finalement accompli cela dans le fichier de composition comme ceci:

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

Le premier élément du tableau ( ${DOCKER_KRITNER_NGINX}:/config) prend une nouvelle variable d'environnement qui mappe le répertoire hôte (défini dans la variable) vers le /configcontenu du conteneur docker lui-même. Cela signifie que l' hôte docker (au chemin d'env var) contiendra la même configuration que le conteneur docker dans la partie secondaire du mappage de volume ( /config)

Le deuxième élément ( ./nginx.conf:/config/nginx/site-confs/default) mappe mon fichier nginx.conf de référentiels locaux (le fichier dans lequel j'ai configuré le proxy inverse) pour remplacer le /config/nginx/site-confs/defaultfichier sur l'hôte et le conteneur du docker.

La liste complète des fichiers que j'ai fini par devoir modifier pour ma situation particulière était:

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

La dnsimple.iniconfiguration était d'ajouter ma clé API, et les …/defaultmaisons de la configuration nginx.

La defaultconfiguration finale avec laquelle je me suis retrouvé est:

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;

Il y a quelques changements par rapport à la valeur par défaut, que je vais essayer de mettre en évidence ensuite.

upstream app_servers { server kritnerwebsite:5000;}

Ci-dessus, c'est plutôt cool, car docker a son propre DNS interne (je suppose?). Vous pouvez configurer un serveur en amont par le nom des conteneurs, dans mon cas «kritnerwebsite». (Remarque: je l'ai changé par rapport au début de l'article, qui était «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