11#! /bin/bash
22set -e
33
4- # URLs used when requesting certificates.
5- # These are picked up from the environment if they are set, which enables
6- # advanced usage of custom ACME servers, else it will use the default Let's
7- # Encrypt servers defined here.
8- : " ${CERTBOT_PRODUCTION_URL=https:// acme-v02.api.letsencrypt.org/ directory} "
9- : " ${CERTBOT_STAGING_URL=https:// acme-staging-v02.api.letsencrypt.org/ directory} "
10-
114# Source in util.sh so we can have our nice tools.
125. " $( cd " $( dirname " $0 " ) " ; pwd) /util.sh"
136
147info " Starting certificate renewal process"
158
9+ # If we have a config file we parse it and let definitions within take
10+ # precedence over any environment variables.
11+ config_file=" ${NGINX_CERTBOT_CONFIG_FILE:-/ etc/ nginx-certbot/ config.yml} "
12+ if [ -f " ${config_file} " ]; then
13+ certbot_authenticator=" $( shyaml get-value certbot.authenticator ' ' < " ${config_file} " ) "
14+ certbot_elliptic_curve=" $( shyaml get-value certbot.elliptic-curve ' ' < " ${config_file} " ) "
15+ certbot_email=" $( shyaml get-value certbot.email ' ' < " ${config_file} " ) "
16+ certbot_key_type=" $( shyaml get-value certbot.key-type ' ' < " ${config_file} " ) "
17+ certbot_rsa_key_size=" $( shyaml get-value certbot.rsa-key-size ' ' < " ${config_file} " ) "
18+ certbot_staging=" $( shyaml get-value certbot.staging ' ' < " ${config_file} " ) "
19+ certbot_production_url=" $( shyaml get-value certbot.production_url ' ' < " ${config_file} " ) "
20+ certbot_staging_url=" $( shyaml get-value certbot.staging_url ' ' < " ${config_file} " ) "
21+ fi
22+
23+ # Environment variable fallbacks
24+ : " ${certbot_authenticator:= ${CERTBOT_AUTHENTICATOR:- webroot} } "
25+ : " ${certbot_elliptic_curve:= ${ELLIPTIC_CURVE:- secp256r1} } "
26+ : " ${certbot_email:= ${CERTBOT_EMAIL} } "
27+ : " ${certbot_key_type:= $( [[ ${USE_ECDSA} == "0" ]] && echo " rsa" || echo " ecdsa" )} "
28+ : " ${certbot_rsa_key_size:= ${RSA_KEY_SIZE:- 2048} } "
29+ : " ${certbot_staging:= ${STAGING} } "
30+
31+ # URLs used when requesting certificates.
32+ # These are picked up from the environment if they are set, which enables
33+ # advanced usage of custom ACME servers, else it will use the default Let's
34+ # Encrypt servers defined here.
35+ : " ${certbot_production_url:= ${CERTBOT_PRODUCTION_URL:- https:// acme-v02.api.letsencrypt.org/ directory} } "
36+ : " ${certbot_staging_url:= ${CERTBOT_STAGING_URL:- https:// acme-staging-v02.api.letsencrypt.org/ directory} } "
37+
1638# We require an email to be able to request a certificate.
17- if [ -z " ${CERTBOT_EMAIL } " ]; then
39+ if [ -z " ${certbot_email } " ]; then
1840 error " CERTBOT_EMAIL environment variable undefined; certbot will do nothing!"
1941 exit 1
2042fi
2143
44+ # Log the global defaults we have resolved so far
45+ debug " Configuration resolved from config file and environment variables:"
46+ for var in certbot_authenticator certbot_elliptic_curve certbot_email certbot_key_type \
47+ certbot_rsa_key_size certbot_staging certbot_production_url certbot_staging_url; do
48+ debug " - ${var} =${! var} "
49+ done
50+
2251# Use the correct challenge URL depending on if we want staging or not.
23- if [ " ${STAGING } " = " 1" ]; then
52+ if [ " ${certbot_staging } " = " 1" ]; then
2453 debug " Using staging environment"
25- letsencrypt_url=" ${CERTBOT_STAGING_URL } "
54+ letsencrypt_url=" ${certbot_staging_url } "
2655else
2756 debug " Using production environment"
28- letsencrypt_url=" ${CERTBOT_PRODUCTION_URL} "
29- fi
30-
31- # Ensure that an RSA key size is set.
32- if [ -z " ${RSA_KEY_SIZE} " ]; then
33- debug " RSA_KEY_SIZE unset, defaulting to 2048"
34- RSA_KEY_SIZE=2048
35- fi
36-
37- # Ensure that an elliptic curve is set.
38- if [ -z " ${ELLIPTIC_CURVE} " ]; then
39- debug " ELLIPTIC_CURVE unset, defaulting to 'secp256r1'"
40- ELLIPTIC_CURVE=" secp256r1"
57+ letsencrypt_url=" ${certbot_production_url} "
4158fi
4259
4360if [ " ${1} " = " force" ]; then
5370# $2: String with all requested domains (e.g. -d domain.org -d www.domain.org)
5471# $3: Type of key algorithm to use (rsa or ecdsa)
5572# $4: The authenticator to use to solve the challenge
73+ # $5: The RSA key size (--rsa-key-size)
74+ # $6: The elliptic curve (--elliptic-curve)
75+ # $7: Credentials file for the authenticator
5676get_certificate () {
77+ local cert_name=" ${1} "
78+ local domain_request=" ${2} "
79+ local key_type=" ${3} "
5780 local authenticator=" ${4,,} "
81+ local rsa_key_size=" ${5:- certbot_rsa_key_size} "
82+ local elliptic_curve=" ${6:- certbot_elliptic_curve} "
83+ local credentials=" ${7} "
5884 local authenticator_params=" "
5985 local challenge_type=" "
6086
@@ -72,9 +98,9 @@ get_certificate() {
7298 return 1
7399 fi
74100 else
75- local configfile=" /etc/letsencrypt/${authenticator# dns-} .ini"
101+ local configfile=" ${credentials :- / etc/ letsencrypt/ ${authenticator# dns-} .ini} "
76102 if [ ! -f " ${configfile} " ]; then
77- error " Authenticator is '${authenticator} ' but '${configfile} ' is missing"
103+ error " Authenticator '${authenticator} ' requires credentials but '${configfile} ' is missing"
78104 return 1
79105 fi
80106 authenticator_params=" --${authenticator} -credentials=${configfile} "
@@ -84,86 +110,143 @@ get_certificate() {
84110 authenticator_params=" ${authenticator_params} --${authenticator} -propagation-seconds=${CERTBOT_DNS_PROPAGATION_SECONDS} "
85111 fi
86112 else
87- error " Unknown authenticator '${authenticator} ' for '${1 } '"
113+ error " Unknown authenticator '${authenticator} ' for '${cert_name } '"
88114 return 1
89115 fi
90116
91- info " Requesting an ${3 ^^} certificate for '${1 } ' (${challenge_type} through ${authenticator} )"
117+ info " Requesting an ${key_type ^^} certificate for '${cert_name } ' (${challenge_type} through ${authenticator} )"
92118 certbot certonly \
93119 --agree-tos --keep -n --text \
94120 --preferred-challenges ${challenge_type} \
95121 --authenticator ${authenticator} \
96122 ${authenticator_params} \
97- --email " ${CERTBOT_EMAIL } " \
123+ --email " ${certbot_email } " \
98124 --server " ${letsencrypt_url} " \
99- --rsa-key-size " ${RSA_KEY_SIZE } " \
100- --elliptic-curve " ${ELLIPTIC_CURVE } " \
101- --key-type " ${3 } " \
102- --cert-name " ${1 } " \
103- ${2 } \
125+ --rsa-key-size " ${rsa_key_size } " \
126+ --elliptic-curve " ${elliptic_curve } " \
127+ --key-type " ${key_type } " \
128+ --cert-name " ${cert_name } " \
129+ ${domain_request } \
104130 --debug ${force_renew}
105131}
106132
107133# Get all the cert names for which we should create certificate requests and
108134# have them signed, along with the corresponding server names.
109- #
110- # This will return an associative array that looks something like this:
111- # "cert_name" => "server_name1 server_name2"
112- declare -A certificates
113- for conf_file in /etc/nginx/conf.d/* .conf* ; do
114- parse_config_file " ${conf_file} " certificates
115- done
135+ # If we have a config file we request certificates based on the specifications
136+ # within that file otherwise we parse the nginx config files to automatically
137+ # discover certificate names, key types, authenticators, and domains.
138+ if [ -f " ${config_file} " ]; then
139+ debug " Using config file '${config_file} ' for certificate specifications"
140+ # Loop over the certificates array and request the certificates
141+ while read -r -d ' ' cert; do
142+ debug " Parsing certificate specification"
116143
117- # Iterate over each key and make a certificate request for them.
118- for cert_name in " ${! certificates[@]} " ; do
119- server_names=(${certificates["$cert_name"]} )
120-
121- # Determine which type of key algorithm to use for this certificate
122- # request. Having the algorithm specified in the certificate name will
123- # take precedence over the environmental variable.
124- if [[ " ${cert_name,,} " =~ (^| [-.])ecdsa([-.]| $) ]]; then
125- debug " Found variant of 'ECDSA' in name '${cert_name} "
126- key_type=" ecdsa"
127- elif [[ " ${cert_name,,} " =~ (^| [-.])ecc([-.]| $) ]]; then
128- debug " Found variant of 'ECC' in name '${cert_name} "
129- key_type=" ecdsa"
130- elif [[ " ${cert_name,,} " =~ (^| [-.])rsa([-.]| $) ]]; then
131- debug " Found variant of 'RSA' in name '${cert_name} "
132- key_type=" rsa"
133- elif [ " ${USE_ECDSA} " == " 0" ]; then
134- key_type=" rsa"
135- else
136- key_type=" ecdsa"
137- fi
144+ # cert-name (required)
145+ cert_name=" $( shyaml get-value cert-name ' ' <<< " ${cert}" ) "
146+ if [ -z " ${cert_name} " ]; then
147+ error " 'cert-name' is missing; ignoring this certificate specification"
148+ continue
149+ fi
150+ debug " Certificate cert-name is: ${cert_name} "
138151
139- # Determine the authenticator to use to solve the authentication challenge.
140- # Having the authenticator specified in the certificate name will take
141- # precedence over the environmental variable.
142- if [[ " ${cert_name,,} " =~ (^| [-.])webroot([-.]| $) ]]; then
143- authenticator=" webroot"
144- debug " Found mention of 'webroot' in name '${cert_name} "
145- elif [[ " ${cert_name,,} " =~ (^| [-.])(dns-($( echo ${CERTBOT_DNS_AUTHENTICATORS} | sed ' s/ /|/g' ) ))([-.]| $) ]]; then
146- authenticator=${BASH_REMATCH[2]}
147- debug " Found mention of authenticator '${authenticator} ' in name '${cert_name} '"
148- elif [ -n " ${CERTBOT_AUTHENTICATOR} " ]; then
149- authenticator=" ${CERTBOT_AUTHENTICATOR} "
150- else
151- authenticator=" webroot"
152- fi
152+ # domains (required)
153+ domains=()
154+ while read -r -d ' ' domain; do
155+ domains+=(" ${domain} " )
156+ done < <( shyaml get-values-0 domains ' ' <<< " ${cert}" )
157+ if [ " ${# domains[@]} " -eq 0 ]; then
158+ error " 'domains' are missing; ignoring this certificate specification"
159+ continue
160+ fi
161+ debug " Certificate domains are is: ${domains[*]} "
162+ domain_request=" "
163+ for domain in " ${domains[@]} " ; do
164+ domain_request+=" --domain ${domain} "
165+ done
166+ debug " Certificate domain request is: ${domain_request} "
167+
168+ # key-type (optional)
169+ key_type=$( shyaml get-value key-type " ${certbot_key_type} " <<< " ${cert}" )
170+ debug " Certificate key-type is: ${key_type} "
171+
172+ # authenticator (optional)
173+ authenticator=$( shyaml get-value authenticator " ${certbot_authenticator} " <<< " ${cert}" )
174+ debug " Certificate authenticator is: ${authenticator} "
175+
176+ # credentials (optional)
177+ credentials=$( shyaml get-value credentials ' ' <<< " ${cert}" )
178+ debug " Certificate authenticator credentials is: ${credentials} "
153179
154- # Assemble the list of domains to be included in the request from
155- # the parsed 'server_names'
156- domain_request=" "
157- for server_name in " ${server_names[@]} " ; do
158- domain_request=" ${domain_request} -d ${server_name} "
180+ # rsa-key-size (optional)
181+ rsa_key_size=$( shyaml get-value rsa-key-size " ${certbot_rsa_key_size} " <<< " ${cert}" )
182+ debug " Certificate RSA key size is: ${rsa_key_size} "
183+
184+ # elliptic-curve (optional)
185+ elliptic_curve=$( shyaml get-value elliptic-curve " ${certbot_elliptic_curve} " <<< " ${cert}" )
186+ debug " Certificate elliptic curve is: ${elliptic_curve} "
187+
188+ # Hand over all the info required for the certificate request, and
189+ # let certbot decide if it is necessary to update the certificate.
190+ if ! get_certificate " ${cert_name} " " ${domain_request} " " ${key_type} " " ${authenticator} " " ${rsa_key_size} " " ${elliptic_curve} " " ${credentials} " ; then
191+ error " Certbot failed for '${cert_name} '. Check the logs for details."
192+ fi
193+ done < <( shyaml -y get-values-0 certificates ' ' < ${config_file} )
194+ else
195+ debug " Using automatic discovery of nginx conf file for certificate specifications"
196+ # This will return an associative array that looks something like this:
197+ # "cert_name" => "server_name1 server_name2"
198+ declare -A certificates
199+ for conf_file in /etc/nginx/conf.d/* .conf* ; do
200+ parse_config_file " ${conf_file} " certificates
159201 done
160202
161- # Hand over all the info required for the certificate request, and
162- # let certbot decide if it is necessary to update the certificate.
163- if ! get_certificate " ${cert_name} " " ${domain_request} " " ${key_type} " " ${authenticator} " ; then
164- error " Certbot failed for '${cert_name} '. Check the logs for details."
165- fi
166- done
203+ # Iterate over each key and make a certificate request for them.
204+ for cert_name in " ${! certificates[@]} " ; do
205+ server_names=(${certificates["$cert_name"]} )
206+
207+ # Determine which type of key algorithm to use for this certificate
208+ # request. Having the algorithm specified in the certificate name will
209+ # take precedence over the environmental variable.
210+ if [[ " ${cert_name,,} " =~ (^| [-.])ecdsa([-.]| $) ]]; then
211+ debug " Found variant of 'ECDSA' in name '${cert_name} "
212+ key_type=" ecdsa"
213+ elif [[ " ${cert_name,,} " =~ (^| [-.])ecc([-.]| $) ]]; then
214+ debug " Found variant of 'ECC' in name '${cert_name} "
215+ key_type=" ecdsa"
216+ elif [[ " ${cert_name,,} " =~ (^| [-.])rsa([-.]| $) ]]; then
217+ debug " Found variant of 'RSA' in name '${cert_name} "
218+ key_type=" rsa"
219+ else
220+ key_type=" ${certbot_key_type} "
221+ fi
222+
223+ # Determine the authenticator to use to solve the authentication challenge.
224+ # Having the authenticator specified in the certificate name will take
225+ # precedence over the environmental variable.
226+ if [[ " ${cert_name,,} " =~ (^| [-.])webroot([-.]| $) ]]; then
227+ authenticator=" webroot"
228+ debug " Found mention of 'webroot' in name '${cert_name} "
229+ elif [[ " ${cert_name,,} " =~ (^| [-.])(dns-($( echo ${CERTBOT_DNS_AUTHENTICATORS} | sed ' s/ /|/g' ) ))([-.]| $) ]]; then
230+ authenticator=${BASH_REMATCH[2]}
231+ debug " Found mention of authenticator '${authenticator} ' in name '${cert_name} '"
232+ else
233+ authenticator=" ${certbot_authenticator} "
234+ fi
235+
236+ # Assemble the list of domains to be included in the request from
237+ # the parsed 'server_names'
238+ domain_request=" "
239+ for server_name in " ${server_names[@]} " ; do
240+ domain_request=" ${domain_request} -d ${server_name} "
241+ done
242+
243+ # Hand over all the info required for the certificate request, and
244+ # let certbot decide if it is necessary to update the certificate.
245+ if ! get_certificate " ${cert_name} " " ${domain_request} " " ${key_type} " " ${authenticator} " ; then
246+ error " Certbot failed for '${cert_name} '. Check the logs for details."
247+ fi
248+ done
249+ fi
167250
168251# After trying to get all our certificates, auto enable any configs that we
169252# did indeed get certificates for.
0 commit comments