blob: 82886ee65b2fca47043e5d13995ac8aa798204ab (
plain)
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
|
#!/bin/bash
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
# PainlessLE – A wrapper script for Certbot [Thomas Lange <code@nerdmind.de>] #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
# #
# Easily get an X.509 certificate from the Let's Encrypt Certificate Authority #
# for a bunch of hostnames without having an HTTP server installed. The script #
# assumes that you have an existing private key stored within your desired #
# install directory (with the filename which is defined in "${CONFIDENTIAL}"). #
# #
# USAGE: #
# painless-le [OPTIONS] TARGET_DIR DNS_DOMAIN [DNS_DOMAIN ...] #
# #
# TARGET_DIR: Path to the target directory for the certificate files. #
# DNS_DOMAIN: One or more DNS hostnames to include in the certficate. #
# #
# OPTION [-K]: Filename of the existing private key in target directory. #
# OPTION [-I]: Filename for the intermediate certificate in target directory. #
# OPTION [-C]: Filename for the standalone certificate in target directory. #
# OPTION [-F]: Filename for the certificate+intermediate in target directory. #
# #
#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%#
#===============================================================================
# Normalize command-line arguments with GNU getopt
#===============================================================================
set -- $(getopt -uo K:I:C:F: -- "$@")
#===============================================================================
# Parse command-line options with getopts
#===============================================================================
while getopts :K:I:C:F: option
do
case $option in
K) ARGUMENT_CONFIDENTIAL="$OPTARG" ;;
I) ARGUMENT_INTERMEDIATE="$OPTARG" ;;
C) ARGUMENT_CERTIFICATE_ONLY="$OPTARG" ;;
F) ARGUMENT_CERTIFICATE_FULL="$OPTARG" ;;
esac
done; shift $((OPTIND-1))
#===============================================================================
# Set positional argument variables
#===============================================================================
TARGET_DIR="$1"
DNS_DOMAIN="${@:2}"
#===============================================================================
# Check if required positional arguments are given
#===============================================================================
[ -z "${TARGET_DIR}" ] && echo "$0: Missing argument: TARGET_DIR" >&2
[ -z "${DNS_DOMAIN}" ] && echo "$0: Missing argument: DNS_DOMAIN" >&2
#===============================================================================
# Exit script if required positional argument is missing
#===============================================================================
[ -z "${TARGET_DIR}" ] || [ -z "${DNS_DOMAIN}" ] && exit 1
#===============================================================================
# Define the ACME endpoint address
#===============================================================================
LETSENCRYPT_ENDPOINT="https://acme-v02.api.letsencrypt.org/directory"
#LETSENCRYPT_ENDPOINT="https://acme-staging-v02.api.letsencrypt.org/directory"
#===============================================================================
# Define commands who are executed BEFORE and AFTER the ACME challenge
#===============================================================================
#LETSENCRYPT_COMMAND_BEFORE="systemctl stop apache2"
#LETSENCRYPT_COMMAND_AFTER="systemctl start apache2"
#===============================================================================
# Define required paths
#===============================================================================
OPENSSLCONF="/etc/ssl/openssl.cnf"
REQUESTFILE=`mktemp /tmp/painless-le.XXXXXXXXXX.csr`
CONFIDENTIAL="${TARGET_DIR%/}/${ARGUMENT_CONFIDENTIAL:-confidential.pem}"
INTERMEDIATE="${TARGET_DIR%/}/${ARGUMENT_INTERMEDIATE:-intermediate.pem}"
CERTIFICATE_ONLY="${TARGET_DIR%/}/${ARGUMENT_CERTIFICATE_ONLY:-certificate_only.pem}"
CERTIFICATE_FULL="${TARGET_DIR%/}/${ARGUMENT_CERTIFICATE_FULL:-certificate_full.pem}"
#===============================================================================
# Delete Certificate-Signing-Request (CSR) file on exit
#===============================================================================
trap 'rm ${REQUESTFILE}' EXIT
#===============================================================================
# Generate Certificate-Signing-Request (CSR)
#===============================================================================
openssl req -config <(cat "${OPENSSLCONF}" <(printf "[SAN]\nsubjectAltName=DNS:`echo ${DNS_DOMAIN} | sed "s/ /,DNS:/g"`")) \
-new -sha256 -key "${CONFIDENTIAL}" -out "${REQUESTFILE}" -outform der -reqexts SAN -subj "/"
#===============================================================================
# Checking if Certificate-Signing-Request (CSR) was successfully created
#===============================================================================
if [ $? != 0 ]; then
echo "$0: Certificate-Signing-Request (CSR) could not be created!" >&2
exit 1
fi
#===============================================================================
# Execute defined command BEFORE the ACME challenge is started
#===============================================================================
[ ! -z "${LETSENCRYPT_COMMAND_BEFORE}" ] && eval $LETSENCRYPT_COMMAND_BEFORE
#===============================================================================
# Execute Let's Encrypt and accomplish the ACME challenge to get the certificate
#===============================================================================
certbot certonly --authenticator standalone --server "${LETSENCRYPT_ENDPOINT}" --csr "${REQUESTFILE}" \
--cert-path "${CERTIFICATE_ONLY}.$$" --fullchain-path "${CERTIFICATE_FULL}.$$" --chain-path "${INTERMEDIATE}.$$"
#===============================================================================
# Checking if Certbot has successfully accomplished the ACME challenge
#===============================================================================
if [ $? != 0 ]; then
echo "$0: Certbot could not successfully accomplish the ACME challenge." >&2
exit 1
fi
#===============================================================================
# Replace previous certificates with the new obtained certificate files
#===============================================================================
[ -f "${INTERMEDIATE}.$$" ] && mv "${INTERMEDIATE}.$$" "${INTERMEDIATE}"
[ -f "${CERTIFICATE_ONLY}.$$" ] && mv "${CERTIFICATE_ONLY}.$$" "${CERTIFICATE_ONLY}"
[ -f "${CERTIFICATE_FULL}.$$" ] && mv "${CERTIFICATE_FULL}.$$" "${CERTIFICATE_FULL}"
#===============================================================================
# Adjust the UNIX permissions with owner and group for the new created files
#===============================================================================
chmod --reference "${CONFIDENTIAL}" "${INTERMEDIATE}" "${CERTIFICATE_ONLY}" "${CERTIFICATE_FULL}"
chown --reference "${CONFIDENTIAL}" "${INTERMEDIATE}" "${CERTIFICATE_ONLY}" "${CERTIFICATE_FULL}"
#===============================================================================
# Execute defined command AFTER the ACME challenge is completed
#===============================================================================
[ ! -z "${LETSENCRYPT_COMMAND_AFTER}" ] && eval $LETSENCRYPT_COMMAND_AFTER
|