From 5d4d2de910652efd8e563278dff6c61ec153fc32 Mon Sep 17 00:00:00 2001 From: Yann Autissier Date: Wed, 2 Nov 2022 12:42:27 +0000 Subject: [PATCH] wip: letsencrypt --- README.md | 67 ++++++++++++++++++++++++++++++++++++++++-- make/def.mk | 1 - stack/ipfs.mk | 4 +-- stack/node.mk | 81 +++++++++++++++++++++++++++++++-------------------- 4 files changed, 116 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 5f7afd3..55efcb6 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ Install myos on a server and manage server config with ansible. * DEBUG -Show executed commands +Show executed commands. ```shell $ make up DEBUG=true @@ -61,7 +61,7 @@ $ make up DEBUG=true * DRYRUN -Do nothing, show commands instead of executing it +Do nothing, show commands instead of executing it. ```shell $ make up DRYRUN=true @@ -69,7 +69,7 @@ $ make up DRYRUN=true * VERBOSE -Show called functions +Show called functions. ```shell $ make up VERBOSE=true @@ -81,6 +81,67 @@ $ make up VERBOSE=true $ make print-VARIABLE ``` +#### Setup + +* SETUP_LETSENCRYPT + +Generate ${DOMAIN} certificate files with letsencrypt. + +By default, myos generates invalid ${DOMAIN} certificate files with openssl. +You can use letsencrypt instead, to generate valid wildcard certificate files. + +To achieve this, you must add following DNS entries to domain ${DOMAIN} to prove you own it: + +``` +_acme-challenge.${DOMAIN} IN CNAME ${DOMAIN}.acme.${DOMAIN}. +acme.${DOMAIN}. IN NS certbot.${DOMAIN}. +certbot.${DOMAIN}. IN A ${DOCKER_HOST_INET4} +``` + +In this config, DOCKER_HOST_INET4 should be the external IP address of the server running certbot. +Port 53 of this IP address must be reachable from internet and point to this server. + +If you want a simple DNS configuration to host all your services on the same server, you can setup following DNS config: + +``` +@ IN A ${DOCKER_HOST_INET4} +*.${DOMAIN}. IN CNAME ${DOMAIN}. +_acme-challenge.${DOMAIN} IN CNAME ${DOMAIN}.acme.${DOMAIN}. +acme.${DOMAIN}. IN NS ${DOMAIN}. +``` + +This will point domain ${DOMAIN} to the IP address ${DOCKER_HOST_INET4} of this server, and point all subdomains *.{DOMAIN} to the ip address pointed by ${DOMAIN}. + +At this point, you should be able to generate a valid certificate for *.${DOMAIN} using certbot [dns standalone](https://github.com/siilike/certbot-dns-standalone) plugin. +This task is done automatically when creating the node stack if SETUP_LETSENCRYPT variable is not empty. + +If you already launched myos node stack before, the ${DOMAIN} certificates has been automatically generated by openssl and you should remove them before trying to generate them with letsencrypt. + +``` +$ make node-down +$ docker volume rm node_myos +``` + +You can then test the letsencrypt certificate generation using DEBUG mode that force to use the letsencrypt staging server. + +``` +$ make node SETUP_LETSENCRYPT=true DEBUG=true +``` + +If letsencrypt certificate generation fails, you can retry the generation of a staging certificate. + +``` +$ make node-certbot-staging +``` + +Once the certificate generation is working, you can ask for a valid certificate. + +``` +$ make node-down +$ docker volume rm node_myos +$ make node SETUP_LETSENCRYPT=true +``` + ### Debug * Show docker compose yaml config diff --git a/make/def.mk b/make/def.mk index 344c474..c057592 100644 --- a/make/def.mk +++ b/make/def.mk @@ -41,7 +41,6 @@ CONFIG_REPOSITORY_URI ?= $(shell printf '$(CONFIG_REPOSITORY_URL)\n' | CONFIG_REPOSITORY_URL ?= $(call pop,$(APP_UPSTREAM_REPOSITORY))/$(notdir $(CONFIG)) CONTEXT ?= ENV $(shell awk 'BEGIN {FS="="}; $$1 !~ /^(\#|$$)/ {print $$1}' .env.dist 2>/dev/null) CONTEXT_DEBUG ?= MAKEFILE_LIST DOCKER_ENV_ARGS ENV_ARGS APPS GIT_AUTHOR_EMAIL GIT_AUTHOR_NAME MAKE_DIR MAKE_SUBDIRS MAKE_CMD_ARGS MAKE_ENV_ARGS UID USER -CERTBOT ?= DEBUG ?= DOCKER ?= $(shell type -p docker) DOMAIN ?= localhost diff --git a/stack/ipfs.mk b/stack/ipfs.mk index ef6721c..ad25b78 100644 --- a/stack/ipfs.mk +++ b/stack/ipfs.mk @@ -1,9 +1,9 @@ ENV_VARS += IPFS_DAEMON_ARGS IPFS_PROFILE IPFS_VERSION IPFS_PROFILE ?= $(if $(filter-out amd64 x86_64,$(MACHINE)),lowpower,server) -IPFS_VERSION ?= 0.15.0 +IPFS_VERSION ?= 0.16.0 .PHONY: bootstrap-stack-ipfs -bootstrap-stack-ipfs: ~/.ipfs +bootstrap-stack-ipfs: ~/.ipfs setup-sysctl ~/.ipfs: mkdir -p ~/.ipfs diff --git a/stack/node.mk b/stack/node.mk index 767ae14..7769663 100644 --- a/stack/node.mk +++ b/stack/node.mk @@ -1,10 +1,11 @@ CMDS += node-exec stack-node-exec node-exec:% node-exec@% node-run node-run:% node-run@% node ?= node/node ENV_VARS += DOCKER_HOST_IFACE DOCKER_HOST_INET4 DOCKER_INTERNAL_DOCKER_HOST +SETUP_LETSENCRYPT ?= # target bootstrap-stack-node: Fire node-certbot node-ssl-certs .PHONY: bootstrap-stack-node -bootstrap-stack-node: docker-network-create-$(DOCKER_NETWORK_PUBLIC) $(if $(CERTBOT),node-certbot) node-ssl-certs +bootstrap-stack-node: docker-network-create-$(DOCKER_NETWORK_PUBLIC) $(if $(SETUP_LETSENCRYPT),node-certbot$(if $(DEBUG),-staging)) node-ssl-certs # target node: Fire stack-node-up .PHONY: node @@ -14,32 +15,44 @@ node: stack-node-up .PHONY: node-% node-%: stack-node-%; -# target node-ssl-certs: Create invalid ${DOMAIN}/privkey.pem and ${DOMAIN}/cert.pem certificate files +# target node-ssl-certs: Create invalid ${DOMAIN} certificate files with openssl .PHONY: node-ssl-certs node-ssl-certs: docker run --rm --mount source=$(NODE_DOCKER_VOLUME),target=/certs alpine \ - [ -f /certs/live/$(DOMAIN)/cert.pem -a -f /certs/live/$(DOMAIN)/privkey.pem ] \ - || $(RUN) docker run --rm -e DOMAIN=$(DOMAIN) --mount source=$(NODE_DOCKER_VOLUME),target=/certs alpine sh -c "\ - apk --no-cache add openssl \ - && mkdir -p /certs/live/${DOMAIN} \ - && { [ -f /certs/live/${DOMAIN}/privkey.pem ] || openssl genrsa -out /certs/live/${DOMAIN}/privkey.pem 2048; } \ - && openssl req -key /certs/live/${DOMAIN}/privkey.pem -out /certs/live/${DOMAIN}/cert.pem \ - -addext extendedKeyUsage=serverAuth \ - -addext subjectAltName=DNS:${DOMAIN} \ - -subj \"/C=/ST=/L=/O=/CN=${DOMAIN}\" \ - -x509 -days 365" + [ -f /certs/live/$(DOMAIN)/fullchain.pem -a -f /certs/live/$(DOMAIN)/privkey.pem ] \ + || $(RUN) docker run --rm \ + -e DOMAIN=$(DOMAIN) \ + --mount source=$(NODE_DOCKER_VOLUME),target=/certs \ + alpine sh -c "\ + apk --no-cache add openssl \ + && mkdir -p /certs/live/${DOMAIN} \ + && { [ -f /certs/live/${DOMAIN}/privkey.pem ] || openssl genrsa -out /certs/live/${DOMAIN}/privkey.pem 2048; } \ + && openssl req -key /certs/live/${DOMAIN}/privkey.pem -out /certs/live/${DOMAIN}/cert.pem \ + -addext extendedKeyUsage=serverAuth \ + -addext subjectAltName=DNS:${DOMAIN},DNS:*.${DOMAIN} \ + -subj \"/C=/ST=/L=/O=/CN=${DOMAIN}\" \ + -x509 -days 365 \ + && rm -f /certs/live/${DOMAIN}/fullchain.pem \ + && ln -s cert.pem /certs/live/${DOMAIN}/fullchain.pem \ + " -# target node-certbot: Create letsencrypt ${DOMAIN}/privkey.pem and ${DOMAIN}/cert.pem files +# target node-certbot: Create ${DOMAIN} certificate files with letsencrypt .PHONY: node-certbot node-certbot: node-docker-build-certbot - docker run --rm --mount source=$(NODE_DOCKER_VOLUME),target=/certs alpine [ -f /certs/live/$(DOMAIN)/cert.pem -a -f /certs/live/$(DOMAIN)/privkey.pem ] \ - || $(RUN) docker run --rm --mount source=$(NODE_DOCKER_VOLUME),target=/etc/letsencrypt/ --mount source=$(NODE_DOCKER_VOLUME),target=/var/log/letsencrypt/ -e DOMAIN=$(DOMAIN) --network host node/certbot \ - --non-interactive --agree-tos --email hostmaster@${DOMAIN} certonly \ - --preferred-challenges dns --authenticator dns-standalone \ - --dns-standalone-address=0.0.0.0 \ - --dns-standalone-port=53 \ - -d ${DOMAIN} \ - -d *.${DOMAIN} + docker run --rm --mount source=$(NODE_DOCKER_VOLUME),target=/certs alpine \ + [ -f /certs/live/$(DOMAIN)/cert.pem -a -f /certs/live/$(DOMAIN)/privkey.pem ] \ + || $(RUN) docker run --rm \ + --mount source=$(NODE_DOCKER_VOLUME),target=/etc/letsencrypt/ \ + --mount source=$(NODE_DOCKER_VOLUME),target=/var/log/letsencrypt/ \ + -e DOMAIN=$(DOMAIN) \ + --network host \ + node/certbot \ + --non-interactive --agree-tos --email hostmaster@${DOMAIN} certonly \ + --preferred-challenges dns --authenticator dns-standalone \ + --dns-standalone-address=0.0.0.0 \ + --dns-standalone-port=53 \ + -d ${DOMAIN} \ + -d *.${DOMAIN} # target node-certbot-certificates: List letsencrypt certificates .PHONY: node-certbot-certificates @@ -51,18 +64,24 @@ node-certbot-certificates: node-docker-build-certbot node-certbot-renew: node-docker-build-certbot docker run --rm --mount source=$(NODE_DOCKER_VOLUME),target=/etc/letsencrypt/ --network host node/certbot renew -# target node-certbot-staging: Create staging letsencrypt ${DOMAIN}/privkey.pem and ${DOMAIN}/cert.pem files +# target node-certbot-staging: Create staging ${DOMAIN} certificate files with letsencrypt .PHONY: node-certbot-staging node-certbot-staging: node-docker-build-certbot - docker run --rm --mount source=$(NODE_DOCKER_VOLUME),target=/certs alpine [ -f /certs/live/$(DOMAIN)/cert.pem -a -f /certs/live/$(DOMAIN)/privkey.pem ] \ - || $(RUN) docker run --rm --mount source=$(NODE_DOCKER_VOLUME),target=/etc/letsencrypt/ --mount source=$(NODE_DOCKER_VOLUME),target=/var/log/letsencrypt/ -e DOMAIN=$(DOMAIN) --network host node/certbot \ - --non-interactive --agree-tos --email hostmaster@${DOMAIN} certonly \ - --preferred-challenges dns --authenticator dns-standalone \ - --dns-standalone-address=0.0.0.0 \ - --dns-standalone-port=53 \ - --staging \ - -d ${DOMAIN} \ - -d *.${DOMAIN} + docker run --rm --mount source=$(NODE_DOCKER_VOLUME),target=/certs alpine \ + [ -f /certs/live/$(DOMAIN)/cert.pem -a -f /certs/live/$(DOMAIN)/privkey.pem ] \ + || $(RUN) docker run --rm \ + --mount source=$(NODE_DOCKER_VOLUME),target=/etc/letsencrypt/ \ + --mount source=$(NODE_DOCKER_VOLUME),target=/var/log/letsencrypt/ \ + -e DOMAIN=$(DOMAIN) \ + --network host \ + node/certbot \ + --non-interactive --agree-tos --email hostmaster@${DOMAIN} certonly \ + --preferred-challenges dns --authenticator dns-standalone \ + --dns-standalone-address=0.0.0.0 \ + --dns-standalone-port=53 \ + --staging \ + -d ${DOMAIN} \ + -d *.${DOMAIN} # target node-docker-build-%: Build % docker .PHONY: node-docker-build-%