1
1
#! /bin/bash
2
2
set -e
3
3
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
-
11
4
# Source in util.sh so we can have our nice tools.
12
5
. " $( cd " $( dirname " $0 " ) " ; pwd) /util.sh"
13
6
14
7
info " Starting certificate renewal process"
15
8
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
+
16
38
# We require an email to be able to request a certificate.
17
- if [ -z " ${CERTBOT_EMAIL } " ]; then
39
+ if [ -z " ${certbot_email } " ]; then
18
40
error " CERTBOT_EMAIL environment variable undefined; certbot will do nothing!"
19
41
exit 1
20
42
fi
21
43
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
+
22
51
# Use the correct challenge URL depending on if we want staging or not.
23
- if [ " ${STAGING } " = " 1" ]; then
52
+ if [ " ${certbot_staging } " = " 1" ]; then
24
53
debug " Using staging environment"
25
- letsencrypt_url=" ${CERTBOT_STAGING_URL } "
54
+ letsencrypt_url=" ${certbot_staging_url } "
26
55
else
27
56
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} "
41
58
fi
42
59
43
60
if [ " ${1} " = " force" ]; then
53
70
# $2: String with all requested domains (e.g. -d domain.org -d www.domain.org)
54
71
# $3: Type of key algorithm to use (rsa or ecdsa)
55
72
# $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
56
76
get_certificate () {
77
+ local cert_name=" ${1} "
78
+ local domain_request=" ${2} "
79
+ local key_type=" ${3} "
57
80
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} "
58
84
local authenticator_params=" "
59
85
local challenge_type=" "
60
86
@@ -72,9 +98,9 @@ get_certificate() {
72
98
return 1
73
99
fi
74
100
else
75
- local configfile=" /etc/letsencrypt/${authenticator# dns-} .ini"
101
+ local configfile=" ${credentials :- / etc/ letsencrypt/ ${authenticator# dns-} .ini} "
76
102
if [ ! -f " ${configfile} " ]; then
77
- error " Authenticator is '${authenticator} ' but '${configfile} ' is missing"
103
+ error " Authenticator '${authenticator} ' requires credentials but '${configfile} ' is missing"
78
104
return 1
79
105
fi
80
106
authenticator_params=" --${authenticator} -credentials=${configfile} "
@@ -84,86 +110,143 @@ get_certificate() {
84
110
authenticator_params=" ${authenticator_params} --${authenticator} -propagation-seconds=${CERTBOT_DNS_PROPAGATION_SECONDS} "
85
111
fi
86
112
else
87
- error " Unknown authenticator '${authenticator} ' for '${1 } '"
113
+ error " Unknown authenticator '${authenticator} ' for '${cert_name } '"
88
114
return 1
89
115
fi
90
116
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} )"
92
118
certbot certonly \
93
119
--agree-tos --keep -n --text \
94
120
--preferred-challenges ${challenge_type} \
95
121
--authenticator ${authenticator} \
96
122
${authenticator_params} \
97
- --email " ${CERTBOT_EMAIL } " \
123
+ --email " ${certbot_email } " \
98
124
--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 } \
104
130
--debug ${force_renew}
105
131
}
106
132
107
133
# Get all the cert names for which we should create certificate requests and
108
134
# 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"
116
143
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} "
138
151
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} "
153
179
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
159
201
done
160
202
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
167
250
168
251
# After trying to get all our certificates, auto enable any configs that we
169
252
# did indeed get certificates for.
0 commit comments