/
lets-encrypt-wrapper
executable file
·342 lines (268 loc) · 7.8 KB
/
lets-encrypt-wrapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
#!/bin/bash
usage() {
local op="$1"
local -A msgs=(
[default]="
Usage: ${0##*/} <op> [args]
Known operations:
enable
disable
cron
renew
"
[enable]="
Usage: ${0##*/} $op <vhost>
Enables Let's Encrypt and issues a new certificate for the
specified vhost (with the domains currently added to the domain).
"
[disable]="
Usage: ${0##*/} $op <vhost>
Disables Let's Encrypt for the specified vhost.
"
[renew]="
Usage: ${0##*/} $op [opts] <vhost>
Options:
-f force the renewal, even if the certficate is not
close to expire
-d domain main domain of the certificate (optional)
Renew the certificate of the specified vhost.
"
)
if [ -z "$op" ]; then
echo "${msgs[default]}"
elif [ -n "${msgs[$op]}" ]; then
echo "${msgs[$op]}"
else
echo "Error: unknown operation" 1>&2
fi
exit 1
}
disable_lets_encrypt_for_vhost() {
local vhost="$1"
save_opts_in_vhost_config "$vhost" \
"- ssl.enabled" \
"- ssl.type"
}
set_lets_encrypt_metadata_for_vhost() {
local vhost="$1"
save_opts_in_vhost_config "$vhost" \
"ssl.enabled = yes" \
"ssl.type = lets-encrypt"
}
cleanup() {
if [ -n "$moved_htaccess" ]; then
su -l -s /bin/bash -c "mv -n $htaccess_file_moved $htaccess_file"
fi
}
# main
[ -z "$1" ] && usage
declare -a acme_args
self_bin=`readlink -e "$0"`
if [ $? -ne 0 ]; then
echo "Error: unable to get self path" 1>&2
exit 1
fi
self_dir="${self_bin%/*}"
sys_dir="${self_dir%/*}"
lib_f="$sys_dir/lib/functions"
if ! . "$lib_f"; then
echo "Error: unable to import $lib_f" 1>&2
exit 1
fi
load_devpanel_config || exit $?
acme_bin="$sys_dir/bin/packages/acme.sh/acme.sh"
if [ ! -f "$acme_bin" -a -x "$acme_bin" ]; then
error "missing executable binary file at $acme_bin"
fi
hash -p "$acme_bin" acme.sh
op="$1"
shift
umask 022
case "$op" in
enable)
[ $# -lt 1 -o -z "$1" ] && usage "$op"
while [ -n "$1" -a "${1:0:1}" == - ]; do
opt="$1"
case "$opt" in
--include-base-domain)
include_base_domain=yes
shift
;;
*)
error "unknown option '$opt'"
;;
esac
done
vhost="$1"
shift
if ! "$sys_dir/libexec/check-vhost-name" archive "$vhost"; then
exit 1
fi
if ! load_vhost_config "$vhost"; then
error "unable to load vhost configuration."
fi
if is_lets_encrypt_enabled_for_vhost "$vhost"; then
error "Let's Encrypt is already enabled on vhost $vhost."
elif is_ssl_enabled_on_vhost "$vhost"; then
errmsg="SSL is already enabled on this vhost."
errmsg+=" Please disable it first."
error "$errmsg"
fi
linux_user="$v__vhost__linux_user"
server_base_domain="$lamp__apache_vhosts__virtwww_domain"
certs_dir="$v__ssl__dir/lets-encrypt"
if [ -d "$certs_dir" ]; then
chmod 700 "$certs_dir"
else
if ! mkdir -m 700 "$certs_dir"; then
error "unable to create directory '$certs_dir'"
fi
fi
acme_args+=( --home "$certs_dir" --renew-hook "$self_bin post-renew" )
public_dir="$v__vhost__document_root"
if [ ! -d "$public_dir" ]; then
error "public directory not found: $public_dir"
fi
if [ -z "$include_base_domain" ] && \
is_to_setup_lets_encrypt_on_base_domain ; then
include_base_domain=yes
fi
declare -a domain_args=()
unset first_domain
for tmp_domain in $v__vhost__domains; do
if [[ "$tmp_domain" != *.* ]]; then
# domain doesn't include a dot, so consider it a local domain, as
# it'll not be possible to validate it on Let's Encrypt
continue
elif [[ "$tmp_domain" == *.$server_base_domain ]] && \
! is_var_set_to_yes include_base_domain ; then
# domain is the base domain, but adding the base domain to LE is not
# enabled
continue
else
if [ ${#domain_args[@]} -eq 0 ] ; then
first_domain="$tmp_domain"
fi
domain_args+=( -d "$tmp_domain" )
fi
done
if [ ${#domain_args[@]} -eq 0 ]; then
error "vhost '$vhost' doesn't have any custom domain. Need to add one first before using Let's Encrypt"
fi
trap cleanup EXIT
htaccess_file="$public_dir/.htaccess"
htaccess_file_moved="$htaccess_file.${RANDOM}_${RANDOM}"
# if the .htaccess file exists, then move it out temporarily because
# many apps have .htaccess rules that block the web validation of Let's
# Encrypt. Will be brought back after the certificate generation
unset moved_htaccess
if [ -f "$htaccess_file" ]; then
su -l -s /bin/bash -c "mv -n $htaccess_file $htaccess_file_moved" "$linux_user"
if [ $? -eq 0 ]; then
moved_htaccess=1
fi
fi
# the first domain on command line is the one used as the name of the
# directory that stores the certificates
cert_dir="$certs_dir/$first_domain"
ca_file="$cert_dir/ca.cer"
ca_bundle_file="$cert_dir/fullchain.cer"
csr_file="$cert_dir/$first_domain.csr"
cert_file="$cert_dir/$first_domain.cer"
priv_key_file="$cert_dir/$first_domain.key"
acme.sh --issue --force "${acme_args[@]}" \
-w "$public_dir" "${domain_args[@]}"
if [ $? -eq 0 ]; then
set_lets_encrypt_metadata_for_vhost "$vhost"
for tmp_ext in conf key header; do
if ls "$certs_dir/"*.$tmp_ext &>/dev/null; then
chmod 600 "$certs_dir/"*.$tmp_ext
fi
if ls "$certs_dir/ca/"*.$tmp_ext &>/dev/null; then
chmod 600 "$certs_dir/ca/"*.$tmp_ext
fi
done
chmod 600 "$priv_key_file"
chmod 644 "$ca_file" "$ca_bundle_file" "$csr_file" "$cert_file"
else
error "unable to generate certificate"
fi
echo
echo "Configuring certificates on Apache..."
"$sys_dir/libexec/apply-ssl-config" -C "$ca_bundle_file" \
-c "$cert_file" -k "$priv_key_file" "$vhost"
;;
cron)
for vhost in $(get_list_of_vhosts); do
load_vhost_config "$vhost" || continue
if is_lets_encrypt_enabled_for_vhost "$vhost"; then
le_dir="$v__ssl__dir/lets-encrypt"
if [ -d "$le_dir" ]; then
acme.sh --home "$le_dir" --cron
fi
fi
done
exit 0
;;
renew)
getopt_flags='fd:'
unset force domain
while getopts $getopt_flags OPTN; do
case "$OPTN" in
f)
force=1
;;
d)
domain="$OPTARG"
;;
*)
error "unknown option: $OPTN"
;;
esac
done
[ $OPTIND -gt 1 ] && shift $(( $OPTIND - 1 ))
[ -z "$1" ] && usage "$op"
vhost="$1"
if ! "$sys_dir/libexec/check-vhost-name" archive "$vhost"; then
exit 1
fi
if ! load_vhost_config "$vhost"; then
error "unable to load vhost configuration."
fi
if ! is_lets_encrypt_enabled_for_vhost "$vhost"; then
echo "Warning: Let's Encrypt is not enabled for this vhost." 1>&2
exit 0
fi
le_dir="$v__ssl__dir/lets-encrypt"
acme.sh --home "$le_dir" --cron ${force:+--force} \
${domain:+ -d "$domain"}
;;
post-renew)
reload_or_start_apache
;;
disable)
[ $# -lt 1 -o -z "$1" ] && usage "$op"
vhost="$1"
if ! load_vhost_config "$vhost"; then
error "unable to load vhost configuration."
fi
if ! "$sys_dir/libexec/check-vhost-name" archive "$vhost"; then
exit 1
fi
if ! is_lets_encrypt_enabled_for_vhost "$vhost"; then
echo "Warning: Let's Encrypt is not enabled for this vhost." 1>&2
exit 0
fi
"$self_dir/disable-ssl-config" "$vhost"
if [ $? -ne 0 ]; then
error "unable to disable SSL config for vhost $vhost"
fi
if ! disable_lets_encrypt_for_vhost "$vhost"; then
error "unable to remove Let's Encrypt metadata for vhost $vhost"
fi
exit 0
;;
*)
usage
;;
esac