-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathentrypoint.sh
450 lines (374 loc) · 11.7 KB
/
entrypoint.sh
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
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
#!/bin/sh
# Regex for IP address or string without a '.'
IP_REGEX='(^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$)|(^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$)|(^[^\.]+$)'
# Configure letsencrypt
if [ -n "${LE_EMAIL}" ]; then
LE_EXTRA_ARGS="${LE_EXTRA_ARGS} --email ${LE_EMAIL}"
else
LE_EXTRA_ARGS="${LE_EXTRA_ARGS} --register-unsafely-without-email"
fi
if [ -n "${LE_RSA_KEY_SIZE}" ]; then
LE_EXTRA_ARGS="${LE_EXTRA_ARGS} --rsa-key-size ${LE_RSA_KEY_SIZE}"
fi
LE_CMD="certbot certonly -n --logs-dir - -w ${CHROOT_DIR} ${LE_EXTRA_ARGS}"
# Configure haproxy
HAPROXY_CMD="haproxy -W -db -f ${HAPROXY_CONFIG} ${HAPROXY_USER_PARAMS}"
HAPROXY_RESTART_CMD="kill -s HUP 1"
HAPROXY_CHECK_CONFIG_CMD="haproxy -f ${HAPROXY_CONFIG} -c"
# Make dirs and files
mkdir -p /deployment/letsencrypt/live
mkdir -p /deployment/certs
if [ "$DOMAINNAME" == 'localhost' ]; then
# To maintain support for existing setups
unset DOMAINNAME
fi
if [ -n "$DOMAINNAME" ]; then
if [ -n "$DOMAINNAMES" ]; then
DOMAINNAMES="$DOMAINNAME,$DOMAINNAMES"
else
DOMAINNAMES="$DOMAINNAME"
fi
fi
print_help() {
echo "Available commands:"
echo ""
echo "help - Show this help"
echo "run - Run proxy in foreground and monitor config changes, executes check and cron-auto-renewal-init"
echo "check - Check proxy configuration only"
echo "list - List configured domains and their certificate's status"
echo "add - Add a new domain and create a certificate for it"
echo "renew - Renew the certificate for an existing domain. Allows to add additional domain names."
echo "monitor - Monitor the config file and certificates for changes and reload proxy"
echo "remove - Remove and existing domain and its certificate"
echo "auto-renew - Try to automatically renew all installed certificates"
echo "print-pin - Print the public key pin for a given domain for usage with HPKP"
echo "sync-haproxy - Deploy hook for certbot to synchronise haproxy cert chains"
}
check_proxy() {
log_info "Checking HAProxy configuration: $HAPROXY_CONFIG"
$HAPROXY_CHECK_CONFIG_CMD
return $?
}
run_proxy() {
log_info "DOMAINNAMES: ${DOMAINNAMES}"
log_info "HAPROXY_CONFIG: ${HAPROXY_CONFIG}"
log_info "HAPROXY_CMD: ${HAPROXY_CMD}"
log_info "HAPROXY_USER_PARAMS: ${HAPROXY_USER_PARAMS}"
log_info "PROXY_LOGLEVEL: ${PROXY_LOGLEVEL}"
log_info "LUA_PATH: ${LUA_PATH}"
log_info "CERT_DIR: ${CERT_DIR}"
log_info "LE_DIR: ${LE_DIR}"
log_info "LE_CMD: ${LE_CMD}"
log_info "AWS_ROUTE53_ROLE: ${AWS_ROUTE53_ROLE}"
if check_proxy; then
log_info "Starting crond"
crond
if [ -n "${AWS_ROUTE53_ROLE}" ]; then
log_info "Creating AWS CLI config file"
mkdir ~/.aws
rm -f ~/.aws/config 2> /dev/null
echo "[default]" >> ~/.aws/config
echo "role_arn = ${AWS_ROUTE53_ROLE}" >> ~/.aws/config
echo "credential_source = Ec2InstanceMetadata" >> ~/.aws/config
echo "" >> ~/.aws/config
fi
cert_init&
log_info "HAProxy starting"
exec su haproxy -s /bin/sh -c "$HAPROXY_CMD $HAPROXY_START_OPTIONS"
ret=$?
if [ $ret -ne 0 ]; then
log_info "HAProxy start failed"
else
log_info "HAProxy started with '$HAPROXY_CONFIG' config, pid = $(cat $HAPROXY_PID_FILE)."
fi
else
log_error "Cannot start proxy until config file errors are resolved in '$HAPROXY_CONFIG'"
exit 1
fi
}
monitor() {
while true; do
log_info "Monitoring config file '$HAPROXY_CONFIG' and certs in '$CERT_DIR' for changes..."
# Wait if config or certificates were changed, block this execution
inotifywait -q -r --exclude '\.git/' -e modify,create,delete,move,move_self "$HAPROXY_CONFIG" "$CERT_DIR"
log_info "Change detected..." &&
sleep 5 &&
restart
done
}
restart() {
log_info "HAProxy restart required..."
if check_proxy; then
log_info "Config is valid so requesting restart..."
$HAPROXY_RESTART_CMD
else
log_info "HAProxy config invalid, not restarting..."
fi
}
add() {
if [ $# -lt 1 ]
then
echo 'Usage: add <domain name> <alternative domain name>...'
return 1
fi
DOMAIN="${1}"
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
RENEWED_LINEAGE="${LE_DIR}/live/${FNAME}"
DOMAIN_FOLDER=$RENEWED_LINEAGE
# Basic invalid DOMAIN check
# Current ash shell on alpine needs regex to be quoted this isn't the case for newer bash shell versions hence the double check
if [[ "$DOMAIN" =~ $IP_REGEX ]] || [[ "$DOMAIN" =~ "$IP_REGEX" ]]; then
log_info "Domain is an IP address or simple hostname so ignoring cert request '$DOMAIN'"
return 2
fi
if [ -e "${DOMAIN_FOLDER}" ]; then
log_error "Domain '${DOMAIN}' already exists."
return 3
fi
log_info "Adding domain \"${DOMAIN}\"..."
DOMAIN_ARGS="-d ${DOMAIN}"
for name in "${@}"; do
if [ "${name}" != "${DOMAIN}" ]; then
DOMAIN_ARGS="${DOMAIN_ARGS} -d ${name}"
fi
done
# For wildcard domains we use route 53 DNS plugin
if [[ ${DOMAIN:0:2} == "*." ]]; then
log_info "Wildcard domain cert requested, using route53 plugin: ${DOMAIN}"
CMD="${LE_CMD} --dns-route53 --cert-name _${DOMAIN:1}"
else
CMD="${LE_CMD} --webroot"
fi
eval "$CMD $DOMAIN_ARGS"
ret=$?
if [ $ret -ne 0 ]; then
>&2 log_error "Failed to generate certificate either haproxy configuration is incorrect or TLD not supported"
return $ret
fi
sync_haproxy
ret=$?
if [ $ret -ne 0 ]; then
>&2 log_error "Failed to create haproxy.pem file for '$DOMAIN'"
return $ret
fi
log_info "Added domain \"${DOMAIN}\"..."
}
renew() {
if [ $# -lt 1 ]
then
echo 'Usage: renew <domain name> <alternative domain name>...'
return 1
fi
DOMAIN="${1}"
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
DOMAIN_FOLDER="${LE_DIR}/live/${FNAME}"
if [ ! -d "${DOMAIN_FOLDER}" ]; then
log_error "Domain ${DOMAIN} does not exist! Cannot renew it."
return 6
fi
log_info "Renewing domain \"${DOMAIN}\"..."
DOMAIN_ARGS="-d ${DOMAIN}"
for name in "${@}"; do
if [ "${name}" != "${DOMAIN}" ]; then
DOMAIN_ARGS="${DOMAIN_ARGS} -d ${name}"
fi
done
eval "$LE_CMD --force-renewal --deploy-hook \"/entrypoint.sh sync-haproxy\" --expand $DOMAIN_ARGS --cert-name $FNAME"
LE_RESULT=$?
if [ ${LE_RESULT} -ne 0 ]; then
>&2 log_error "letsencrypt returned error code ${LE_RESULT}"
return ${LE_RESULT}
fi
log_info "Renewed domain \"${DOMAIN}\"..."
}
auto_renew() {
log_info "Executing auto renew at $(date -R)"
certbot renew --deploy-hook "/entrypoint.sh sync-haproxy"
}
list() {
eval "$LE_CMD certificates"
}
print_pin() {
if [ $# -lt 1 ]
then
echo 'Usage: print-pin <domain name>'
return 1
fi
DOMAIN="${1}"
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
DOMAIN_FOLDER="${LE_DIR}/live/${DOMAIN}"
if [ ! -d "${DOMAIN_FOLDER}" ]; then
log_error "Domain ${DOMAIN} does not exist!"
return 6
fi
pin_sha256=$(openssl rsa -in "${DOMAIN_FOLDER}/privkey.pem" -outform der -pubout 2> /dev/null | openssl dgst -sha256 -binary | openssl enc -base64)
echo
echo "pin-sha256: ${pin_sha256}"
echo
echo "Example usage in HTTP header:"
echo "Public-Key-Pins: pin-sha256=""${pin_sha256}""; max-age=5184000; includeSubdomains;"
echo
echo "CAUTION: Make sure to also add another pin for a backup key!"
}
remove() {
if [ $# -lt 1 ]
then
echo 'Usage: remove <domain name>'
return 1
fi
DOMAIN=$1
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
DOMAIN_LIVE_FOLDER="${LE_DIR}/live/${FNAME}"
DOMAIN_ARCHIVE_FOLDER="${LE_DIR}/archive/${FNAME}"
DOMAIN_RENEWAL_CONFIG="${LE_DIR}/renewal/${FNAME}.conf"
log_info "Removing domain \"${DOMAIN}\"..."
if [ ! -d "${DOMAIN_LIVE_FOLDER}" ]; then
log_error "Domain ${1} does not exist! Cannot remove it."
return 5
fi
certbot revoke -n --cert-name ${FNAME}
certbot delete -n --cert-name ${FNAME}
log_info "Removed domain \"${DOMAIN}\"..."
}
log_error() {
>&2 echo "[ERROR][$(date +'%Y-%m-%d %T')] $*"
}
log_info() {
echo "[INFO][$(date +'%Y-%m-%d %T')] $*"
}
die() {
echo >&2 "$*"
exit 1
}
cert_init() {
log_info "cert_init...waiting 10s for haproxy to be ready"
sleep 10
log_info "Executing cert_init at $(date -R)"
# Take checksum of haproxy certs so we can tell if we need to restart as inotify is not running yet
CERT_SHA1=$(find ${CERT_DIR} -type f -print0 | xargs -0 sha1sum)
# Iterate through domain names and check/create certificates
# certbot certificates doesn't seem to work so check directories exist manually
IFS_OLD=$IFS
IFS=$','
i=0
for DOMAIN in $DOMAINNAMES; do
i=$((i+1))
FNAME="${DOMAIN}"
if [[ ${DOMAIN:0:2} == "*." ]]; then
FNAME="_${DOMAIN:1}"
fi
if [ ! -d "${LE_DIR}/live/${FNAME}" ]; then
log_info "Initialising certificate for '${DOMAIN}'..."
rm -rf "${LE_DIR}/live/${FNAME}" 2>/dev/null
add "${DOMAIN}"
fi
if [ $i -eq 1 ]; then
log_info "Symlinking first domain to built in cert directory to take precedence over self signed cert"
ln -sfT ${CERT_DIR}/${FNAME} /etc/haproxy/certs/00-cert
fi
done
IFS=$IFS_OLD
# Remove any stale/obsolete certificates and check haproxy full chain file exists
DIRS=$(ls -1d ${LE_DIR}/live/* 2>/dev/null)
IFS_OLD=$IFS
IFS=$'\n'
for d in $DIRS; do
# Need additional check as ash shell ls -d includes files
if [ ! -d "$d" ]; then
continue
fi
CERT=$(basename $d)
if [[ ${CERT:0:2} == "_." ]]; then
CERT="*${CERT:1}"
fi
if [[ "$DOMAINNAMES" != "$CERT"* ]] && [[ "$DOMAINNAMES" != *",$CERT"* ]]; then
log_info "Removing obsolete certificate for '$CERT'"
remove "$CERT"
else
CERT=$(basename $d)
RENEWED_LINEAGE="$LE_DIR/live/$CERT"
sync_haproxy
fi
done
IFS=$IFS_OLD
# Remove any stale haproxy cert chains
FILES=$(ls -1 ${CERT_DIR}/* 2>/dev/null)
IFS_OLD=$IFS
IFS=$'\n'
for f in $FILES; do
CERT=$(basename $f)
if [ ! -d "${LE_DIR}/live/${CERT}" ]; then
log_info "Removing obsolete haproxy certificate chain for '$CERT'"
rm -f $f
fi
done
IFS=$IFS_OLD
# Run renew in case any existing certs need updating
auto_renew
CERT_SHA2=$(find ${CERT_DIR} -type f -print0 | xargs -0 sha1sum)
if [ "$CERT_SHA1" != "$CERT_SHA2" ]; then
log_info "HAProxy certs have been modified so restarting"
restart
fi
log_info "Starting monitoring process"
monitor
}
sync_haproxy() {
if [ -z "$RENEWED_LINEAGE" ]; then
log_error "sync-haproxy expect RENEWED_LINEAGE variable to be set"
exit 1
fi
DOMAIN_FOLDER="$RENEWED_LINEAGE"
DOMAIN=$(basename $RENEWED_LINEAGE)
log_info "Updating haproxy cert chain for '$DOMAIN'"
cat "${DOMAIN_FOLDER}/privkey.pem" \
"${DOMAIN_FOLDER}/fullchain.pem" \
> "/tmp/haproxy.pem"
mv "/tmp/haproxy.pem" "${CERT_DIR}/${DOMAIN}"
}
if [ $# -eq 0 ]
then
print_help
exit 0
fi
CMD="${1}"
shift
if [ "${CMD}" = "run" ]; then
run_proxy "${@}"
elif [ "${CMD}" = "restart" ]; then
restart
elif [ "${CMD}" = "monitor" ]; then
monitor
elif [ "${CMD}" = "check" ]; then
check_proxy "${@}"
elif [ "${CMD}" = "add" ]; then
add "${@}"
elif [ "${CMD}" = "list" ]; then
list "${@}"
elif [ "${CMD}" = "remove" ]; then
remove "${@}"
elif [ "${CMD}" = "renew" ]; then
renew "${@}"
elif [ "${CMD}" = "auto-renew" ]; then
auto_renew "${@}"
elif [ "${CMD}" = "help" ]; then
print_help "${@}"
elif [ "${CMD}" = "print-pin" ]; then
print_pin "${@}"
elif [ "${CMD}" = "sync-haproxy" ]; then
sync_haproxy
else
die "Unknown command: ${CMD}"
fi