Skip to content

Commit 6fec053

Browse files
authored
Merge pull request #85 from Sharpie/SUP-4347-handle-puppet-6-CAs
(SUP-4347) Allow extend.sh to operate on Puppet 6 CAs
2 parents 9184360 + 443ccca commit 6fec053

File tree

1 file changed

+186
-35
lines changed

1 file changed

+186
-35
lines changed

files/extend.sh

Lines changed: 186 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,73 @@
11
#!/bin/bash
22

3-
# Credit: https://github.com/Sharpie
3+
# Puppet CA extension script
4+
#
5+
# This script uses the Puppet CA certificates and private
6+
# keys to generate new CA certificates with extended 15 year
7+
# lifespans.
8+
#
9+
# This script operates on 2-certificate CA bundles used by
10+
# Puppet 6 and later in addition to single-certificate
11+
# bundles used by Puppet 5 and earlier.
12+
#
13+
# This script requires that the default locations for
14+
# the Puppet cadir and files underneath it are in use.
15+
#
16+
# Externally issued CA certificates are not supported.
417

518
set -e
619

720
PUPPET_BIN='/opt/puppetlabs/puppet/bin'
821

9-
ca_cert=$("${PUPPET_BIN}/puppet" config print --section master cacert)
10-
ca_key=$("${PUPPET_BIN}/puppet" config print --section master cakey)
11-
ca_dir=$(dirname "${ca_cert}")
22+
ca_bundle=$("${PUPPET_BIN}/puppet" config print --section master cacert)
23+
ca_dir=$(dirname "${ca_bundle}")
1224

13-
printf 'CA certificate file: %s\n' "${ca_cert}" >&2
14-
printf 'CA private key file: %s\n' "${ca_key}" >&2
25+
printf 'CA bundle file: %s\n' "${ca_bundle}" >&2
1526

16-
printf '\nChecking CA chain length...\n' >&2
17-
chain_length=$(grep -cF 'BEGIN CERTIFICATE' "${ca_cert}")
27+
printf '\n Checking CA bundle length...\n' >&2
28+
chain_length=$(grep -cF 'BEGIN CERTIFICATE' "${ca_bundle}")
1829

19-
if (( chain_length > 1 )); then
20-
printf '%s certificates were found in: %s\n' "${chain_length}" "${ca_cert}" >&2
21-
printf 'This script only works on CA files that contain a single certificate.\n' >&2
22-
exit 1
23-
elif (( chain_length != 1 )); then
24-
printf 'No certificates found in: %s\n' "${ca_cert}" >&2
30+
if (( chain_length > 2 )); then
31+
printf '%s certificates were found in: %s\n' "${chain_length}" "${ca_bundle}" >&2
32+
printf 'This script only works on CA bundles that contain one or two certificates.\n' >&2
2533
exit 1
34+
elif (( chain_length == 2 )); then
35+
printf '2 entry Puppet CA detected in: %s\n' "${ca_bundle}" >&2
36+
root_key="${ca_dir}/root_key.pem"
37+
intermediate_key="${ca_dir}/ca_key.pem"
38+
39+
[[ -r "${root_key}" ]] || {
40+
printf 'ERROR: The Root CA key file is not readable: %s\n' "${root_key}" >&2
41+
printf 'This script must be run as root and does not support externally issued CA certs.\n' >&2
42+
exit 1
43+
}
44+
45+
[[ -r "${intermediate_key}" ]] || {
46+
printf 'ERROR: The Intermediate CA key file is not readable: %s\n' "${root_key}" >&2
47+
exit 1
48+
}
49+
elif (( chain_length == 1 )); then
50+
printf '1 entry Puppet CA detected in: %s\n' "${ca_bundle}" >&2
51+
root_key="${ca_dir}/ca_key.pem"
52+
53+
[[ -r "${root_key}" ]] || {
54+
printf 'ERROR: The Root CA key file is not readable: %s\n' "${root_key}" >&2
55+
printf 'This script must be run as root and does not support externally issued CA certs.\n' >&2
56+
exit 1
57+
}
2658
else
27-
printf '%s certificate found in: %s\n' "${chain_length}" "${ca_cert}" >&2
59+
printf 'ERROR: No certificates detected in: %s\n' "${ca_bundle}" >&2
60+
exit 1
2861
fi
2962

30-
# Compute start and end dates for new certificate.
31-
# Formats the year as YY instead of YYYY because the latter isn't supported
32-
# until OpenSSL 1.1.1.
33-
start_date=$(date -u --date='-24 hours' '+%y%m%d%H%M%SZ')
34-
end_date=$(date -u --date='+15 years' '+%y%m%d%H%M%SZ')
35-
ca_serial_num=$("${PUPPET_BIN}/openssl" x509 -in "${ca_cert}" -noout -serial|cut -d= -f2)
3663

3764
# Build a temporary directory with files required to renew the CA cert.
38-
workdir=$(mktemp -d -t renew_ca_cert.XXX)
39-
printf '%s' "${ca_serial_num}" > "${workdir}/serial"
65+
66+
workdir=$(mktemp -d -t puppet_ca_extend.XXX)
67+
printf 'Using working directory: %s\n' "${workdir}" >&2
68+
4069
touch "${workdir}/inventory"
4170
touch "${workdir}/inventory.attr"
42-
4371
cat <<EOT > "${workdir}/openssl.cnf"
4472
[ca]
4573
default_ca=ca_settings
@@ -59,27 +87,150 @@ commonName=supplied
5987
basicConstraints=critical,CA:TRUE
6088
keyUsage=keyCertSign,cRLSign
6189
subjectKeyIdentifier=hash
62-
authorityKeyIdentifier=issuer:always
90+
authorityKeyIdentifier=keyid:always
6391
EOT
6492

93+
# Separate CA bundle out into individual certificates
94+
csplit -szf "${workdir}/puppet-ca-cert-" "${ca_bundle}" '/-----BEGIN CERTIFICATE-----/' '{*}'
95+
ca_certs=("${workdir}"/puppet-ca-cert-*)
96+
97+
98+
# Match keys up with certificates
99+
root_cert=''
100+
101+
root_fingerprint=$("${PUPPET_BIN}/openssl" rsa -in "${root_key}" -noout -modulus|cut -d= -f2-)
102+
for ca_cert in "${ca_certs[@]}"; do
103+
ca_fingerprint=$("${PUPPET_BIN}/openssl" x509 -in "${ca_cert}" -noout -modulus|cut -d= -f2-)
104+
if [[ "${ca_fingerprint}" == "${root_fingerprint}" ]]; then
105+
root_cert="${ca_cert}"
106+
break
107+
fi
108+
done
109+
110+
[[ -n "${root_cert}" ]] || {
111+
printf 'ERROR: Could not find a certificate matching key %s\n' "${root_key}" >&2
112+
printf 'Checked: %s\n\t%s\n' "${ca_certs[@]}" >&2
113+
114+
exit 1
115+
}
116+
117+
if (( chain_length == 2 )); then
118+
intermediate_cert=''
119+
120+
intermediate_fingerprint=$("${PUPPET_BIN}/openssl" rsa -in "${intermediate_key}" -noout -modulus|cut -d= -f2-)
121+
for ca_cert in "${ca_certs[@]}"; do
122+
ca_fingerprint=$("${PUPPET_BIN}/openssl" x509 -in "${ca_cert}" -noout -modulus|cut -d= -f2-)
123+
if [[ "${ca_fingerprint}" == "${intermediate_fingerprint}" ]]; then
124+
intermediate_cert="${ca_cert}"
125+
break
126+
fi
127+
done
128+
129+
[[ -n "${intermediate_cert}" ]] || {
130+
printf 'ERROR: Could not find a certificate matching key %s\n' "${intermediate_key}" >&2
131+
printf 'Checked: %s\n\t%s\n' "${ca_certs[@]}" >&2
132+
133+
exit 1
134+
}
135+
fi
136+
137+
138+
# Extend CA certs
139+
140+
# Compute start and end dates for new certificates.
141+
# Formats the year as YY instead of YYYY because the latter isn't supported
142+
# until OpenSSL 1.1.1.
143+
start_date=$(date -u --date='-24 hours' '+%y%m%d%H%M%SZ')
144+
end_date=$(date -u --date='+15 years' '+%y%m%d%H%M%SZ')
145+
146+
root_subject=$("${PUPPET_BIN}/openssl" x509 -in "${root_cert}" -noout -subject|cut -d= -f2-)
147+
root_issuer=$("${PUPPET_BIN}/openssl" x509 -in "${root_cert}" -noout -issuer|cut -d= -f2-)
148+
root_enddate=$("${PUPPET_BIN}/openssl" x509 -in "${root_cert}" -noout -enddate|cut -d= -f2-)
149+
root_serial_num=$("${PUPPET_BIN}/openssl" x509 -in "${root_cert}" -noout -serial|cut -d= -f2-)
150+
151+
[[ "${root_subject}" = "${root_issuer}" ]] || {
152+
printf 'ERROR: Root CA cert is not self-signed: %s\n' "${root_cert}" >&2
153+
printf 'Subject: %s\n' "${root_subject}" >&2
154+
printf 'Issuer: %s\n' "${root_issuer}" >&2
155+
printf 'This script does not support externally-issued CAs.' >&2
156+
157+
exit 1
158+
}
159+
160+
printf '\nExtending: %s\n' "${root_cert}" >&2
161+
printf 'Subject: %s\n' "${root_subject}" >&2
162+
printf 'Issuer: %s\n' "${root_issuer}" >&2
163+
printf 'Serial: %s\n' "${root_serial_num}" >&2
164+
printf 'End-Date: %s\n' "${root_enddate}" >&2
165+
65166
# Generate a signing request from the existing certificate
66167
"${PUPPET_BIN}/openssl" x509 -x509toreq \
67-
-in "${ca_cert}" \
68-
-signkey "${ca_key}" \
69-
-out "${workdir}/ca_csr.pem"
168+
-in "${root_cert}" \
169+
-signkey "${root_key}" \
170+
-out "${workdir}/root_ca.csr.pem"
70171

71-
# Sign the request
72-
new_ca_cert="${ca_dir}/ca_crt-expires-${end_date}.pem"
172+
printf '%s' "${root_serial_num}" > "${workdir}/serial"
73173

74174
yes | "${PUPPET_BIN}/openssl" ca \
75175
-notext \
76-
-in "${workdir}/ca_csr.pem" \
77-
-keyfile "${ca_key}" \
176+
-in "${workdir}/root_ca.csr.pem" \
177+
-keyfile "${root_key}" \
78178
-config "${workdir}/openssl.cnf" \
79179
-selfsign \
80180
-startdate "${start_date}" \
81181
-enddate "${end_date}" \
82-
-out "${new_ca_cert}" >&2
182+
-out "${workdir}/root_ca.renewed.pem" >&2
183+
184+
if (( chain_length == 2 )); then
185+
intermediate_subject=$("${PUPPET_BIN}/openssl" x509 -in "${intermediate_cert}" -noout -subject|cut -d= -f2-)
186+
intermediate_issuer=$("${PUPPET_BIN}/openssl" x509 -in "${intermediate_cert}" -noout -issuer|cut -d= -f2-)
187+
intermediate_enddate=$("${PUPPET_BIN}/openssl" x509 -in "${intermediate_cert}" -noout -enddate|cut -d= -f2-)
188+
intermediate_serial_num=$("${PUPPET_BIN}/openssl" x509 -in "${intermediate_cert}" -noout -serial|cut -d= -f2-)
189+
190+
[[ "${intermediate_issuer}" == "${root_issuer}" ]] || {
191+
printf 'ERROR: Intermediate CA cert is not issued by Root CA: %s\n' "${intermediate_cert}" >&2
192+
printf 'Subject: %s\n' "${intermediate_subject}" >&2
193+
printf 'Issuer: %s\n' "${intermediate_issuer}" >&2
194+
printf 'This script does not support externally-issued CAs.' >&2
195+
196+
exit 1
197+
}
198+
199+
printf '\nExtending: %s\n' "${intermediate_cert}" >&2
200+
printf 'Subject: %s\n' "${intermediate_subject}" >&2
201+
printf 'Issuer: %s\n' "${intermediate_issuer}" >&2
202+
printf 'Serial: %s\n' "${intermediate_serial_num}" >&2
203+
printf 'End-Date: %s\n' "${intermediate_enddate}" >&2
204+
205+
# Generate a signing request from the existing certificate
206+
"${PUPPET_BIN}/openssl" x509 -x509toreq \
207+
-in "${intermediate_cert}" \
208+
-signkey "${intermediate_key}" \
209+
-out "${workdir}/intermediate_ca.csr.pem"
210+
211+
printf '%s' "${intermediate_serial_num}" > "${workdir}/serial"
212+
213+
yes | "${PUPPET_BIN}/openssl" ca \
214+
-notext \
215+
-in "${workdir}/intermediate_ca.csr.pem" \
216+
-cert "${workdir}/root_ca.renewed.pem" \
217+
-keyfile "${root_key}" \
218+
-config "${workdir}/openssl.cnf" \
219+
-startdate "${start_date}" \
220+
-enddate "${end_date}" \
221+
-out "${workdir}/intermediate_ca.renewed.pem" >&2
222+
fi
223+
224+
225+
# Generate output bundle
226+
new_ca_bundle="${ca_dir}/ca_crt-expires-${end_date}.pem"
227+
228+
if (( chain_length == 2 )); then
229+
cat "${workdir}/intermediate_ca.renewed.pem" \
230+
"${workdir}/root_ca.renewed.pem" > "${new_ca_bundle}"
231+
else
232+
cat "${workdir}/root_ca.renewed.pem" > "${new_ca_bundle}"
233+
fi
83234

84-
printf '\nRenewed CA certificate\n' >&2
85-
printf '%s\n' "${new_ca_cert}"
235+
printf '\nRenewed CA certificates.\n' >&2
236+
printf '%s\n' "${new_ca_bundle}"

0 commit comments

Comments
 (0)