From 79f9eb6491291ddaa1097dbffe3b4b2cb8a13002 Mon Sep 17 00:00:00 2001 From: Levente F Date: Tue, 10 Oct 2023 12:51:55 +0200 Subject: [PATCH 1/8] Automatic SSL certificate from Let's Encrypt Supports HTTPS with either and automatically obtained certificate or a manually supplied one --- .env.example | 17 +++++++---- README.md | 53 +++++++++++++++++++++++++++------- build.cmd | 3 ++ docker-compose.yml | 14 ++++++++- nginx/Dockerfile | 46 +++++++++++++++++++---------- nginx/setup/cert-entrypoint.sh | 31 ++++++++++++++++++++ nginx/setup/cert-request.sh | 29 +++++++++++++++++++ nginx/setup/default.conf | 29 ++++++++++++++----- nginx/setup/health-check.sh | 21 ++++++++++++++ 9 files changed, 205 insertions(+), 38 deletions(-) create mode 100644 build.cmd create mode 100644 nginx/setup/cert-entrypoint.sh create mode 100644 nginx/setup/cert-request.sh create mode 100644 nginx/setup/health-check.sh diff --git a/.env.example b/.env.example index 3508b6e7..67a900e9 100644 --- a/.env.example +++ b/.env.example @@ -7,10 +7,17 @@ DATAPUSHER_CONTAINER_NAME=datapusher CKAN_CONTAINER_NAME=ckan WORKER_CONTAINER_NAME=ckan-worker -# Host Ports -CKAN_PORT_HOST=5000 -NGINX_PORT_HOST=81 -NGINX_SSLPORT_HOST=8443 +# Host hostname and ports +CKAN_DOMAIN=localhost +CKAN_PORT_HOST=5000 # Used only in the dev deployment +NGINX_PORT_HOST=80 +NGINX_SSLPORT_HOST=443 + +# SSL certificate details +CKAN_AUTO_CERT=true +CKAN_CERT_EMAIL=your_cert_email@example.com +#CKAN_CERT_OPTIONS= +#CKAN_CERT_RENEW_INTERVAL=12h # CKAN databases POSTGRES_USER=postgres @@ -38,9 +45,9 @@ USE_HTTPS_FOR_DEV=false # CKAN core CKAN_VERSION=2.10.0 CKAN_SITE_ID=default -CKAN_SITE_URL=https://localhost:8443 CKAN_PORT=5000 CKAN_PORT_HOST=5000 +CKAN_SITE_URL=https://${CKAN_DOMAIN}:${NGINX_SSLPORT_HOST} CKAN___BEAKER__SESSION__SECRET=CHANGE_ME # See https://docs.ckan.org/en/latest/maintaining/configuration.html#api-token-settings CKAN___API_TOKEN__JWT__ENCODE__SECRET=string:CHANGE_ME diff --git a/README.md b/README.md index f5548a7c..c160a89c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ * [Datastore and Datapusher](#Datastore-and-datapusher) * [NGINX](#nginx) * [The ckanext-envvars extension](#envvars) -* [The CKAN_SITE_URL parameter](#CKAN_SITE_URL) +* [Connecting directly to CKAN](#connecting-directly) * [Changing the base image](#Changing-the-base-image) * [Replacing DataPusher with XLoader](#Replacing-DataPusher-with-XLoader) @@ -113,11 +113,15 @@ The new extension files and directories are created in the `/srv/app/src_extensi Sometimes is useful to run your local development instance under HTTPS, for instance if you are using authentication extensions like [ckanext-saml2auth](https://github.com/keitaroinc/ckanext-saml2auth). To enable it, set the following in your `.env` file: +``` USE_HTTPS_FOR_DEV=true +``` and update the site URL setting: - CKAN_SITE_URL=https://localhost:5000 +``` + CKAN_SITE_URL=https://${CKAN_DOMAIN}:${CKAN_PORT_HOST} +``` After recreating the `ckan-dev` container, you should be able to access CKAN at https://localhost:5000 @@ -167,7 +171,7 @@ ckan -c /srv/app/ckan.ini validation init-db And then in our `Dockerfile.dev` file we install the extension and copy the initialization scripts: ```Dockerfile -FROM ckan/ckan-base:2.9.7-dev +FROM ckan/ckan-base:2.10.1-dev RUN pip install -e git+https://github.com/frictionlessdata/ckanext-validation.git#egg=ckanext-validation && \ pip install -r https://raw.githubusercontent.com/frictionlessdata/ckanext-validation/master/requirements.txt @@ -175,7 +179,7 @@ RUN pip install -e git+https://github.com/frictionlessdata/ckanext-validation.gi COPY docker-entrypoint.d/* /docker-entrypoint.d/ ``` -NB: There are a number of extension examples commented out in the Dockerfile.dev file +> Note: There are a number of extension examples commented out in the Dockerfile.dev file ## 7. Applying patches @@ -218,11 +222,37 @@ running the latest version of Datapusher. ## 10. NGINX -The base Docker Compose configuration uses an NGINX image as the front-end (ie: reverse proxy). It includes HTTPS running on port number 8443. A "self-signed" SSL certificate is generated as part of the ENTRYPOINT. The NGINX `server_name` directive and the `CN` field in the SSL certificate have been both set to 'localhost'. This should obviously not be used for production. +The base Docker Compose configuration uses NGINX as the front-end (ie: reverse proxy) and +SSL terminator. It accepts HTTPS connections on ports 80 and 443 (port 80 gets redirected to 443). +A "self-signed" SSL certificate is generated when NGINX starts, unless you provide your own +certificate and key (see below). This should obviously not be used for production. + +NGINX can automatically obtain a [Let's Encrypt](https://letsencrypt.org/getting-started/) +SSL certificate and renew it periodically, if you configure the value `CKAN_AUTO_CERT` in +the `.env` file. The hostname and the email address to be used in the certificate request +must be defined in the `.env` file. + +> Note: If you are not using a self-signed certificate, you must define the domain name on +> which CKAN will be published in the variable `CKAN_DOMAIN` in the `.env` file. If you supply +> your own certificate, the configured domain name must match the domain name in the certificate. + +If you want to use your own SSL certificate with CKAN, you must supply the files +`fullchain.pem` and `privkey.pem`, respectively. Consider the following folder structure: -Creating the SSL cert and key files as follows: -`openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=DE/ST=Berlin/L=Berlin/O=None/CN=localhost" -keyout ckan-local.key -out ckan-local.crt` -The `ckan-local.*` files will then need to be moved into the nginx/setup/ directory +``` +nginx +├── certificate +│ ├── my_cert.pem +│ └── my_key.pem +└── Dockerfile +``` + +Then modify the NGINX Dockerfile to use these files: + +``` +COPY certificate/my_cert.pem /usr/share/nginx/certificates/fullchain.pem +COPY certificate/my_key.pem /usr/share/nginx/certificates/privkey.pem +``` ## 11. envvars @@ -245,9 +275,12 @@ These parameters can be added to the `.env` file For more information please see [ckanext-envvars](https://github.com/okfn/ckanext-envvars) -## 12. CKAN_SITE_URL +## 12. Connecting directly -For convenience the CKAN_SITE_URL parameter should be set in the .env file. For development it can be set to http://localhost:5000 and non-development set to https://localhost:8443 +For convenience, in development deployments you can connect directly to the CKAN container +at http://localhost:5000. In non-development environments you can use https://localhost:443. +The hostname is defined in variable `CKAN_DOMAIN`, while the port is defined in +variable `CKAN_PORT_HOST` in the `.env` file. ## 13. Manage new users diff --git a/build.cmd b/build.cmd new file mode 100644 index 00000000..4e86fb18 --- /dev/null +++ b/build.cmd @@ -0,0 +1,3 @@ +@echo off +echo Building CKAN containers... +docker compose -p euh4d-ckan up -d --build --remove-orphans diff --git a/docker-compose.yml b/docker-compose.yml index 0f5330fb..4db41fee 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,14 +13,26 @@ services: build: context: nginx/ dockerfile: Dockerfile + args: + - CKAN_URL=http://ckan:${CKAN_PORT}/ + - DOMAIN=${CKAN_DOMAIN} + - EMAIL=${CKAN_CERT_EMAIL} + - SSL_CERT_OPTIONS=${CKAN_CERT_OPTIONS} + - SSL_CERT_RENEW=${CKAN_CERT_RENEW_INTERVAL} networks: - webnet - ckannet + healthcheck: + test: ['CMD', '/opt/status.sh'] + start_period: 30s + interval: 1m + timeout: 5s depends_on: ckan: condition: service_healthy ports: - - "0.0.0.0:${NGINX_SSLPORT_HOST}:${NGINX_SSLPORT}" + - "0.0.0.0:${NGINX_PORT_HOST}:80" + - "0.0.0.0:${NGINX_SSLPORT_HOST}:443" ckan: container_name: ${CKAN_CONTAINER_NAME} diff --git a/nginx/Dockerfile b/nginx/Dockerfile index eda7994e..e2fa48ed 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,23 +1,39 @@ FROM nginx:stable-alpine +ARG CKAN_URL +ARG DOMAIN +ARG EMAIL +ARG SSL_CERT_OPTIONS +ARG SSL_CERT_RENEW + +ENV CKAN_URL=${CKAN_URL} +ENV DOMAIN=${DOMAIN} +ENV EMAIL=${EMAIL} +ENV SSL_CERT_OPTIONS=${SSL_CERT_OPTIONS} +ENV SSL_CERT_RENEW=${SSL_CERT_RENEW} ENV NGINX_DIR=/etc/nginx RUN apk update --no-cache && \ apk upgrade --no-cache && \ - apk add --no-cache openssl + apk add --no-cache openssl inotify-tools certbot -COPY setup/nginx.conf ${NGINX_DIR}/nginx.conf COPY setup/index.html /usr/share/nginx/html/index.html -COPY setup/default.conf ${NGINX_DIR}/conf.d/ - -RUN mkdir -p ${NGINX_DIR}/certs - -ENTRYPOINT \ - openssl req \ - -subj '/C=DE/ST=Berlin/L=Berlin/O=None/CN=localhost' \ - -x509 -newkey rsa:4096 \ - -nodes -keyout /etc/nginx/ssl/default_key.pem \ - -keyout ${NGINX_DIR}/certs/ckan-local.key \ - -out ${NGINX_DIR}/certs/ckan-local.crt \ - -days 365 && \ - nginx -g 'daemon off;' \ No newline at end of file +COPY setup/nginx.conf ${NGINX_DIR}/nginx.conf +COPY setup/default.conf ${NGINX_DIR}/conf.d/default.conf.template +RUN envsubst '${DOMAIN}${CKAN_URL}' < ${NGINX_DIR}/conf.d/default.conf.template > ${NGINX_DIR}/conf.d/default.conf && \ + rm -f ${NGINX_DIR}/conf.d/default.conf.template && \ + mkdir -p /var/cache/nginx/proxycache && \ + mkdir -p /var/cache/nginx/proxytemp + +WORKDIR /opt +COPY setup/cert-entrypoint.sh entrypoint.sh +COPY setup/cert-request.sh request.sh +COPY setup/health-check.sh status.sh + +RUN chmod a+x entrypoint.sh && \ + chmod a+x request.sh && \ + chmod a+x status.sh + +EXPOSE 80 443 + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/nginx/setup/cert-entrypoint.sh b/nginx/setup/cert-entrypoint.sh new file mode 100644 index 00000000..442656c5 --- /dev/null +++ b/nginx/setup/cert-entrypoint.sh @@ -0,0 +1,31 @@ +#!/bin/sh + +export DOMAIN=${DOMAIN} +export EMAIL=${EMAIL} + +# Ensure we have a folder for the certificates +if [ ! -d /usr/share/nginx/certificates ]; then + echo "Creating certificate folder" + mkdir -p /usr/share/nginx/certificates +fi + +### If certificates do not exist yet, create self-signed ones before we start nginx +if [ ! -f /usr/share/nginx/certificates/fullchain.pem ]; then + echo "Generating self-signed certificate" + openssl genrsa -out /usr/share/nginx/certificates/privkey.pem 4096 + openssl req -new -key /usr/share/nginx/certificates/privkey.pem -out /usr/share/nginx/certificates/cert.csr -nodes -subj \ + "/C=PT/ST=World/L=World/O=$DOMAIN/OU=egi lda/CN=$DOMAIN" + openssl x509 -req -days 365 -in /usr/share/nginx/certificates/cert.csr -signkey /usr/share/nginx/certificates/privkey.pem -out /usr/share/nginx/certificates/fullchain.pem +fi + +if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ] && [ -n "$CKAN_AUTO_CERT" ] && ["$CKAN_AUTO_CERT" != "true"]; then + ### Send certbot emission/renewal to background + $(while :; do /opt/request.sh; sleep "${SSL_CERT_RENEW:-12h}"; done;) & + + ### Check for changes in the certificate (i.e renewals or first start) in the background + $(while inotifywait -e close_write /usr/share/nginx/certificates; do echo "Reloading nginx with new certificate"; nginx -s reload; done) & +fi + +### Start nginx with daemon off as our main pid +echo "Starting nginx" +nginx -g 'daemon off;' diff --git a/nginx/setup/cert-request.sh b/nginx/setup/cert-request.sh new file mode 100644 index 00000000..65303701 --- /dev/null +++ b/nginx/setup/cert-request.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +if [ ! -f /var/www/html ]; then + mkdir -p /var/www/html +fi + +if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ]; then + certbot certonly \ + --config-dir ${LETSENCRYPT_DIR:-/etc/letsencrypt} \ + --dry-run \ + --agree-tos \ + --domains "$DOMAIN" \ + --email $EMAIL \ + --expand \ + --noninteractive \ + --webroot \ + --webroot-path /var/www/html \ + $SSL_CERT_OPTIONS || true + + if [ -f ${LETSENCRYPT_DIR:-/etc/letsencrypt}/live/$DOMAIN/privkey.pem ]; then + chmod +rx ${LETSENCRYPT_DIR:-/etc/letsencrypt}/live + chmod +rx ${LETSENCRYPT_DIR:-/etc/letsencrypt}/archive + chmod +r ${LETSENCRYPT_DIR:-/etc/letsencrypt}/archive/${DOMAIN}/fullchain*.pem + chmod +r ${LETSENCRYPT_DIR:-/etc/letsencrypt}/archive/${DOMAIN}/privkey*.pem + cp ${LETSENCRYPT_DIR:-/etc/letsencrypt}/live/$DOMAIN/privkey.pem /usr/share/nginx/certificates/privkey.pem + cp ${LETSENCRYPT_DIR:-/etc/letsencrypt}/live/$DOMAIN/fullchain.pem /usr/share/nginx/certificates/fullchain.pem + echo "Copied new certificate to /usr/share/nginx/certificates" + fi +fi diff --git a/nginx/setup/default.conf b/nginx/setup/default.conf index a628619f..66ad2dbc 100644 --- a/nginx/setup/default.conf +++ b/nginx/setup/default.conf @@ -1,11 +1,23 @@ server { - #listen 80; - #listen [::]:80; + listen 80 default; + listen [::]:80 default; + server_name $DOMAIN; + + location /.well-known/acme-challenge/ { + root /var/www/html; + } + + location / { + return 301 https://$host$request_uri; + } +} + +server { listen 443 ssl; listen [::]:443 ssl; - server_name localhost; - ssl_certificate /etc/nginx/certs/ckan-local.crt; - ssl_certificate_key /etc/nginx/certs/ckan-local.key; + server_name $DOMAIN; + ssl_certificate /usr/share/nginx/certificates/fullchain.pem; + ssl_certificate_key /usr/share/nginx/certificates/privkey.pem; # TLS 1.2 & 1.3 only ssl_protocols TLSv1.2 TLSv1.3; @@ -21,8 +33,12 @@ server { #access_log /var/log/nginx/host.access.log main; + location /.well-known/acme-challenge/ { + root /var/www/html; + } + location / { - proxy_pass http://ckan:5000/; + proxy_pass $CKAN_URL; proxy_set_header X-Forwarded-For $remote_addr; proxy_set_header Host $host; #proxy_cache cache; @@ -35,7 +51,6 @@ server { error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html; # redirect server error pages to the static page /error.html - # location = /error.html { ssi on; internal; diff --git a/nginx/setup/health-check.sh b/nginx/setup/health-check.sh new file mode 100644 index 00000000..4b7bc43e --- /dev/null +++ b/nginx/setup/health-check.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +FAIL_CODE=6 + +check_status() { + LRED="\033[1;31m" # Light Red + LGREEN="\033[1;32m" # Light Green + NC='\033[0m' # No Color + + curl -sfk "${1}" > /dev/null + + if [ ! $? = ${FAIL_CODE} ]; then + echo -e "${LGREEN}${1} is online${NC}" + exit 0 + else + echo -e "${LRED}${1} is down${NC}" + exit 1 + fi +} + +check_status "${1:-localhost}" From f89239c117c71261423381cc552e230a821b07bc Mon Sep 17 00:00:00 2001 From: Levente F Date: Tue, 10 Oct 2023 13:25:14 +0200 Subject: [PATCH 2/8] Add dedicated volume for the NGINX certificate Allows reusing the certificate, helps with exceeting the rate limit for Let's Encrypt --- README.md | 4 ++++ docker-compose.yml | 3 +++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index c160a89c..c5ec2f38 100644 --- a/README.md +++ b/README.md @@ -254,6 +254,10 @@ COPY certificate/my_cert.pem /usr/share/nginx/certificates/fullchain.pem COPY certificate/my_key.pem /usr/share/nginx/certificates/privkey.pem ``` +> Note: In case you remove the CKAN containers, retain the volume `ssl_cert`, +> which contains the SSL certificate. This will avoid requesting a new one for the same domain, +> in case you redeploy CKAN (prevents exceeding Let's Encrypt rate limit). + ## 11. envvars The ckanext-envvars extension is used in the CKAN Docker base repo to build the base images. diff --git a/docker-compose.yml b/docker-compose.yml index 4db41fee..dea74105 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ volumes: ckan_storage: pg_data: solr_data: + ssl_cert: services: @@ -19,6 +20,8 @@ services: - EMAIL=${CKAN_CERT_EMAIL} - SSL_CERT_OPTIONS=${CKAN_CERT_OPTIONS} - SSL_CERT_RENEW=${CKAN_CERT_RENEW_INTERVAL} + volumes: + - ssl_cert:/etc/letsencrypt networks: - webnet - ckannet From 95aa542a6066965d8c3f1d9a0a69776c5d211e5c Mon Sep 17 00:00:00 2001 From: Levente F Date: Tue, 10 Oct 2023 13:52:00 +0200 Subject: [PATCH 3/8] Made Let's Encrypt dry runs configurable Make the Let's Encrypt folder configurable --- .env.example | 2 ++ README.md | 6 ++++++ docker-compose.yml | 4 +++- nginx/Dockerfile | 4 ++++ nginx/setup/cert-entrypoint.sh | 4 +++- nginx/setup/cert-request.sh | 3 +-- 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index 67a900e9..1632cd42 100644 --- a/.env.example +++ b/.env.example @@ -18,6 +18,8 @@ CKAN_AUTO_CERT=true CKAN_CERT_EMAIL=your_cert_email@example.com #CKAN_CERT_OPTIONS= #CKAN_CERT_RENEW_INTERVAL=12h +LETSENCRYPT_DIR=/etc/letsencrypt +LETSENCRYPT_DRYRUN=--dry-run # CKAN databases POSTGRES_USER=postgres diff --git a/README.md b/README.md index c5ec2f38..70ef94c0 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,12 @@ must be defined in the `.env` file. > which CKAN will be published in the variable `CKAN_DOMAIN` in the `.env` file. If you supply > your own certificate, the configured domain name must match the domain name in the certificate. +> Note: Even when you set `CKAN_AUTO_CERT` to `true` to use a Let's Encrypt SSL certificate, +> NGINX will still use a self-signed certificate and will only do dry runs for requesting a +> certificate to avoid the [rate limits](https://letsencrypt.org/docs/rate-limits/) of +> Let's Encrypt. Once you are satisfied that everything is running as expected, you can comment +> out the setting `LETSENCRYPT_DRYRUN` in the `.env` file. + If you want to use your own SSL certificate with CKAN, you must supply the files `fullchain.pem` and `privkey.pem`, respectively. Consider the following folder structure: diff --git a/docker-compose.yml b/docker-compose.yml index dea74105..07c66f39 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,8 +20,10 @@ services: - EMAIL=${CKAN_CERT_EMAIL} - SSL_CERT_OPTIONS=${CKAN_CERT_OPTIONS} - SSL_CERT_RENEW=${CKAN_CERT_RENEW_INTERVAL} + - LETSENCRYPT_DIR=${LETSENCRYPT_DIR} + - LETSENCRYPT_DRYRUN=${LETSENCRYPT_DRYRUN} volumes: - - ssl_cert:/etc/letsencrypt + - ssl_cert:${LETSENCRYPT_DIR} networks: - webnet - ckannet diff --git a/nginx/Dockerfile b/nginx/Dockerfile index e2fa48ed..25990cfa 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -5,12 +5,16 @@ ARG DOMAIN ARG EMAIL ARG SSL_CERT_OPTIONS ARG SSL_CERT_RENEW +ARG LETSENCRYPT_DIR +ARG LETSENCRYPT_DRYRUN ENV CKAN_URL=${CKAN_URL} ENV DOMAIN=${DOMAIN} ENV EMAIL=${EMAIL} ENV SSL_CERT_OPTIONS=${SSL_CERT_OPTIONS} ENV SSL_CERT_RENEW=${SSL_CERT_RENEW} +ENV LETSENCRYPT_DIR=${LETSENCRYPT_DIR} +ENV LETSENCRYPT_DRYRUN=${LETSENCRYPT_DRYRUN} ENV NGINX_DIR=/etc/nginx RUN apk update --no-cache && \ diff --git a/nginx/setup/cert-entrypoint.sh b/nginx/setup/cert-entrypoint.sh index 442656c5..bd79943d 100644 --- a/nginx/setup/cert-entrypoint.sh +++ b/nginx/setup/cert-entrypoint.sh @@ -2,6 +2,8 @@ export DOMAIN=${DOMAIN} export EMAIL=${EMAIL} +export LETSENCRYPT_DIR=${LETSENCRYPT_DIR} +export LETSENCRYPT_DRYRUN=${LETSENCRYPT_DRYRUN} # Ensure we have a folder for the certificates if [ ! -d /usr/share/nginx/certificates ]; then @@ -18,7 +20,7 @@ if [ ! -f /usr/share/nginx/certificates/fullchain.pem ]; then openssl x509 -req -days 365 -in /usr/share/nginx/certificates/cert.csr -signkey /usr/share/nginx/certificates/privkey.pem -out /usr/share/nginx/certificates/fullchain.pem fi -if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ] && [ -n "$CKAN_AUTO_CERT" ] && ["$CKAN_AUTO_CERT" != "true"]; then +if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ] && ["$CKAN_AUTO_CERT" == "true"]; then ### Send certbot emission/renewal to background $(while :; do /opt/request.sh; sleep "${SSL_CERT_RENEW:-12h}"; done;) & diff --git a/nginx/setup/cert-request.sh b/nginx/setup/cert-request.sh index 65303701..ef9a1523 100644 --- a/nginx/setup/cert-request.sh +++ b/nginx/setup/cert-request.sh @@ -6,8 +6,7 @@ fi if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ]; then certbot certonly \ - --config-dir ${LETSENCRYPT_DIR:-/etc/letsencrypt} \ - --dry-run \ + --config-dir ${LETSENCRYPT_DIR:-/etc/letsencrypt} ${LETSENCRYPT_DRYRUN:---dry-run} \ --agree-tos \ --domains "$DOMAIN" \ --email $EMAIL \ From 694b23caf32ba72bf1b329179a25c7d58fa1091a Mon Sep 17 00:00:00 2001 From: Levente F Date: Tue, 10 Oct 2023 13:55:43 +0200 Subject: [PATCH 4/8] Fix local build script commited by mistake --- build.cmd | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 build.cmd diff --git a/build.cmd b/build.cmd deleted file mode 100644 index 4e86fb18..00000000 --- a/build.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@echo off -echo Building CKAN containers... -docker compose -p euh4d-ckan up -d --build --remove-orphans From c76c86e58a8ac45a9fa51cfd29e63f56fe243637 Mon Sep 17 00:00:00 2001 From: Levente F Date: Tue, 10 Oct 2023 16:31:43 +0200 Subject: [PATCH 5/8] Env vars used as args must be defined --- .env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 1632cd42..2862c37c 100644 --- a/.env.example +++ b/.env.example @@ -16,8 +16,8 @@ NGINX_SSLPORT_HOST=443 # SSL certificate details CKAN_AUTO_CERT=true CKAN_CERT_EMAIL=your_cert_email@example.com -#CKAN_CERT_OPTIONS= -#CKAN_CERT_RENEW_INTERVAL=12h +CKAN_CERT_OPTIONS= +CKAN_CERT_RENEW_INTERVAL=12h LETSENCRYPT_DIR=/etc/letsencrypt LETSENCRYPT_DRYRUN=--dry-run From 518b3759d92a0d7355de0ae86d2afeec013d9f7e Mon Sep 17 00:00:00 2001 From: Levente Farkas Date: Thu, 12 Oct 2023 13:24:28 +0200 Subject: [PATCH 6/8] Added configurable certificate organsation Simplified certificate configuration (most options can be commented out) --- .env.example | 9 +++++---- docker-compose.yml | 10 ++++++---- nginx/Dockerfile | 7 +++++-- nginx/setup/cert-entrypoint.sh | 8 +++++--- nginx/setup/cert-request.sh | 16 ++++++++-------- 5 files changed, 29 insertions(+), 21 deletions(-) diff --git a/.env.example b/.env.example index 2862c37c..37fe39eb 100644 --- a/.env.example +++ b/.env.example @@ -14,11 +14,12 @@ NGINX_PORT_HOST=80 NGINX_SSLPORT_HOST=443 # SSL certificate details -CKAN_AUTO_CERT=true CKAN_CERT_EMAIL=your_cert_email@example.com -CKAN_CERT_OPTIONS= -CKAN_CERT_RENEW_INTERVAL=12h -LETSENCRYPT_DIR=/etc/letsencrypt +CKAN_CERT_ORG=your_organisation +#CKAN_AUTO_CERT=true +#CKAN_CERT_OPTIONS= +#CKAN_CERT_RENEW_INTERVAL=12h +#LETSENCRYPT_DIR=/etc/letsencrypt LETSENCRYPT_DRYRUN=--dry-run # CKAN databases diff --git a/docker-compose.yml b/docker-compose.yml index 07c66f39..e150899e 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,12 +18,14 @@ services: - CKAN_URL=http://ckan:${CKAN_PORT}/ - DOMAIN=${CKAN_DOMAIN} - EMAIL=${CKAN_CERT_EMAIL} - - SSL_CERT_OPTIONS=${CKAN_CERT_OPTIONS} - - SSL_CERT_RENEW=${CKAN_CERT_RENEW_INTERVAL} - - LETSENCRYPT_DIR=${LETSENCRYPT_DIR} + - ORGANISATION=${CKAN_CERT_ORG} + - SSL_AUTO_CERT=${CKAN_AUTO_CERT:-true} + - SSL_CERT_OPTIONS=${CKAN_CERT_OPTIONS:-} + - SSL_CERT_RENEW=${CKAN_CERT_RENEW_INTERVAL:-12h} + - LETSENCRYPT_DIR=${LETSENCRYPT_DIR:-/etc/letsencrypt} - LETSENCRYPT_DRYRUN=${LETSENCRYPT_DRYRUN} volumes: - - ssl_cert:${LETSENCRYPT_DIR} + - ssl_cert:${LETSENCRYPT_DIR:-/etc/letsencrypt} networks: - webnet - ckannet diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 25990cfa..8d8d262b 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -3,18 +3,21 @@ FROM nginx:stable-alpine ARG CKAN_URL ARG DOMAIN ARG EMAIL +ARG ORGANISATION +ARG SSL_AUTO_CERT ARG SSL_CERT_OPTIONS ARG SSL_CERT_RENEW ARG LETSENCRYPT_DIR ARG LETSENCRYPT_DRYRUN -ENV CKAN_URL=${CKAN_URL} ENV DOMAIN=${DOMAIN} ENV EMAIL=${EMAIL} +ENV ORGANISATION=${ORGANISATION} +ENV SSL_AUTO_CERT=${SSL_AUTO_CERT} ENV SSL_CERT_OPTIONS=${SSL_CERT_OPTIONS} ENV SSL_CERT_RENEW=${SSL_CERT_RENEW} -ENV LETSENCRYPT_DIR=${LETSENCRYPT_DIR} ENV LETSENCRYPT_DRYRUN=${LETSENCRYPT_DRYRUN} +ENV LETSENCRYPT_DIR=${LETSENCRYPT_DIR} ENV NGINX_DIR=/etc/nginx RUN apk update --no-cache && \ diff --git a/nginx/setup/cert-entrypoint.sh b/nginx/setup/cert-entrypoint.sh index bd79943d..ee326d15 100644 --- a/nginx/setup/cert-entrypoint.sh +++ b/nginx/setup/cert-entrypoint.sh @@ -2,6 +2,7 @@ export DOMAIN=${DOMAIN} export EMAIL=${EMAIL} +export SSL_CERT_OPTIONS=${SSL_CERT_OPTIONS} export LETSENCRYPT_DIR=${LETSENCRYPT_DIR} export LETSENCRYPT_DRYRUN=${LETSENCRYPT_DRYRUN} @@ -16,13 +17,14 @@ if [ ! -f /usr/share/nginx/certificates/fullchain.pem ]; then echo "Generating self-signed certificate" openssl genrsa -out /usr/share/nginx/certificates/privkey.pem 4096 openssl req -new -key /usr/share/nginx/certificates/privkey.pem -out /usr/share/nginx/certificates/cert.csr -nodes -subj \ - "/C=PT/ST=World/L=World/O=$DOMAIN/OU=egi lda/CN=$DOMAIN" + "/C=PT/ST=World/L=World/O=$ORGANISATION/CN=$DOMAIN" openssl x509 -req -days 365 -in /usr/share/nginx/certificates/cert.csr -signkey /usr/share/nginx/certificates/privkey.pem -out /usr/share/nginx/certificates/fullchain.pem fi -if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ] && ["$CKAN_AUTO_CERT" == "true"]; then +if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ] && [ -n "${SSL_AUTO_CERT}" ] && ${SSL_AUTO_CERT}; then ### Send certbot emission/renewal to background - $(while :; do /opt/request.sh; sleep "${SSL_CERT_RENEW:-12h}"; done;) & + echo "Scheduling periodic check if certificate should be renewed" + $(while :; do /opt/request.sh; sleep "${SSL_CERT_RENEW}"; done;) & ### Check for changes in the certificate (i.e renewals or first start) in the background $(while inotifywait -e close_write /usr/share/nginx/certificates; do echo "Reloading nginx with new certificate"; nginx -s reload; done) & diff --git a/nginx/setup/cert-request.sh b/nginx/setup/cert-request.sh index ef9a1523..13a5cef7 100644 --- a/nginx/setup/cert-request.sh +++ b/nginx/setup/cert-request.sh @@ -6,7 +6,7 @@ fi if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ]; then certbot certonly \ - --config-dir ${LETSENCRYPT_DIR:-/etc/letsencrypt} ${LETSENCRYPT_DRYRUN:---dry-run} \ + --config-dir ${LETSENCRYPT_DIR} ${LETSENCRYPT_DRYRUN} \ --agree-tos \ --domains "$DOMAIN" \ --email $EMAIL \ @@ -16,13 +16,13 @@ if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "localhost" ]; then --webroot-path /var/www/html \ $SSL_CERT_OPTIONS || true - if [ -f ${LETSENCRYPT_DIR:-/etc/letsencrypt}/live/$DOMAIN/privkey.pem ]; then - chmod +rx ${LETSENCRYPT_DIR:-/etc/letsencrypt}/live - chmod +rx ${LETSENCRYPT_DIR:-/etc/letsencrypt}/archive - chmod +r ${LETSENCRYPT_DIR:-/etc/letsencrypt}/archive/${DOMAIN}/fullchain*.pem - chmod +r ${LETSENCRYPT_DIR:-/etc/letsencrypt}/archive/${DOMAIN}/privkey*.pem - cp ${LETSENCRYPT_DIR:-/etc/letsencrypt}/live/$DOMAIN/privkey.pem /usr/share/nginx/certificates/privkey.pem - cp ${LETSENCRYPT_DIR:-/etc/letsencrypt}/live/$DOMAIN/fullchain.pem /usr/share/nginx/certificates/fullchain.pem + if [ -f ${LETSENCRYPT_DIR}/live/$DOMAIN/privkey.pem ]; then + chmod +rx ${LETSENCRYPT_DIR}/live + chmod +rx ${LETSENCRYPT_DIR}/archive + chmod +r ${LETSENCRYPT_DIR}/archive/${DOMAIN}/fullchain*.pem + chmod +r ${LETSENCRYPT_DIR}/archive/${DOMAIN}/privkey*.pem + cp ${LETSENCRYPT_DIR}/live/$DOMAIN/privkey.pem /usr/share/nginx/certificates/privkey.pem + cp ${LETSENCRYPT_DIR}/live/$DOMAIN/fullchain.pem /usr/share/nginx/certificates/fullchain.pem echo "Copied new certificate to /usr/share/nginx/certificates" fi fi From da496871c0631ce3194202f055980a07b934e791 Mon Sep 17 00:00:00 2001 From: Levente Farkas Date: Thu, 12 Oct 2023 16:39:40 +0200 Subject: [PATCH 7/8] Can comment out LETSENCRYPT_DRYRUN in .env file when ready to request certificate --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index e150899e..95a69bc2 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,7 +23,7 @@ services: - SSL_CERT_OPTIONS=${CKAN_CERT_OPTIONS:-} - SSL_CERT_RENEW=${CKAN_CERT_RENEW_INTERVAL:-12h} - LETSENCRYPT_DIR=${LETSENCRYPT_DIR:-/etc/letsencrypt} - - LETSENCRYPT_DRYRUN=${LETSENCRYPT_DRYRUN} + - LETSENCRYPT_DRYRUN=${LETSENCRYPT_DRYRUN:- } volumes: - ssl_cert:${LETSENCRYPT_DIR:-/etc/letsencrypt} networks: From 684a6077dac523b3cd2df6f632db94fc1e90e519 Mon Sep 17 00:00:00 2001 From: Levente Farkas Date: Fri, 24 Nov 2023 10:51:31 +0100 Subject: [PATCH 8/8] Restart Nginx if it stops --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 95a69bc2..5b1d262a 100755 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,5 @@ version: "3" - volumes: ckan_storage: pg_data: @@ -29,6 +28,7 @@ services: networks: - webnet - ckannet + restart: unless-stopped healthcheck: test: ['CMD', '/opt/status.sh'] start_period: 30s