11#! /bin/bash
22# Script to manage the NRLF truststore files
3+ #
4+ # Note that all creative and destructive operations are local only apart from
5+ # "push-all". Files are not uploaded to or deleted from S3 automatically.
6+ #
7+ # Once changes have been made locally and have been tested thoroughly, the files
8+ # in the truststore directories can be uploaded to S3 using the appropriate AWS
9+ # CLI commands or the "push-all" command below.
10+ #
311set -o errexit -o pipefail -o nounset
412
513BUCKET=" nhsd-nrlf--truststore"
@@ -14,8 +22,16 @@ function _truststore_help() {
1422 echo " build-cert <name> <ca> <fqdn> - Build a single client cert + private key"
1523 echo " build-all - Build the standard trust store certs"
1624 echo " pull-ca <ca> - Pull the certificate authority"
25+ echo " pull-ca-key <ca> - Pull the certificate authority private key"
1726 echo " pull-client <env> - pull the files needed for a client connection"
1827 echo " pull-server <env> - pull the files needed for a server connection"
28+ echo " pull-all <env> - pull all the truststore files for an environment"
29+ echo " push-all <env> - push all the truststore files for an environment"
30+ echo " rotate-ca <env> - rotate the certificate authority, archiving the previous one"
31+ echo " rotate-cert <env> - rotate the client certificate, archiving the previous one"
32+ echo " disable-archived-ca <env> - disable an archived certificate authority"
33+ echo " restore-archived-ca <env> - restore an archived certificate authority"
34+ echo " restore-archived-cert <env> - restore an archived client certificate"
1935 echo
2036}
2137
@@ -31,119 +47,293 @@ EOF"
3147 cat > ${outfile} << EOF
3248${output}
3349EOF
50+
51+ return 0
3452}
3553
3654# build a certificate authority
3755function _truststore_build_ca() {
38- if [ $# -ne 2 ]; then
56+ if [[ $# -ne 2 ] ]; then
3957 echo " Usage: $0 build-ca <name> <fqdn>"
40- exit 1
58+ return 1
4159 fi
4260
43- ca =$1
61+ env =$1
4462 fqdn=$2
4563
4664 substitute_env_in_file ./truststore/config/ca.conf /tmp/ca.conf
4765
48- echo -e " 🚧 Building CA: $ca \t(FQDN: $fqdn )"
66+ echo -e " 🚧 Building CA: $env \t(FQDN: $fqdn )"
4967
5068 openssl req -newkey rsa:4096 \
5169 -nodes \
52- -keyout ./truststore/ca/$ca .key \
70+ -keyout ./truststore/ca/$env .key \
5371 -new \
5472 -x509 \
5573 -days 36500 \
56- -out ./truststore/ca/$ca .crt \
74+ -out ./truststore/ca/$env .crt \
5775 -config /tmp/ca.conf \
5876 -extensions v3_req \
5977 -extensions v3_ca & > /dev/null
6078
6179 rm /tmp/ca.conf
62- echo -e " ✅ Successfully Built CA: $ca "
80+ echo -e " ✅ Successfully Built CA: $env (FQDN: $fqdn )"
81+
82+ cat ./truststore/ca/$env .crt > ./truststore/server/$env .pem
83+ echo -e " ✅ Successfully Built Server Truststore: $env "
84+ return 0
6385}
6486
65- # buld a certificate
87+ # build a certificate
6688function _truststore_build_cert() {
67- if [ $# -ne 3 ]; then
89+ if [[ $# -ne 3 ] ]; then
6890 echo " Usage: $0 build-cert <name> <ca> <fqdn>"
69- exit 1;
91+ return 1;
7092 fi
7193
72- client =$1
73- ca =$2
94+ cert_name =$1
95+ ca_name =$2
7496 fqdn=$3
7597 serial=$( date +%s%3N)
7698
7799 substitute_env_in_file ./truststore/config/client.conf /tmp/client.conf
78100
79- echo -e " 🚧 Generating $client keypair"
101+ echo -e " 🚧 Generating $cert_name keypair"
80102
81103 openssl req \
82104 -newkey rsa:4096 \
83105 -nodes \
84- -keyout truststore/client/$client .key \
106+ -keyout truststore/client/$cert_name .key \
85107 -new \
86- -out truststore/client/$client .csr \
108+ -out truststore/client/$cert_name .csr \
87109 -config /tmp/client.conf \
88110 -extensions v3_req \
89111 -extensions usr_cert & > /dev/null
90112
113+ echo -e " 🚧 Signing $cert_name certificate with ca=$ca_name (serial: $serial )"
114+
91115 openssl x509 \
92116 -req \
93- -in truststore/client/$client .csr \
94- -CA truststore/ca/$ca .crt \
95- -CAkey truststore/ca/$ca .key \
117+ -in truststore/client/$cert_name .csr \
118+ -CA truststore/ca/$ca_name .crt \
119+ -CAkey truststore/ca/$ca_name .key \
96120 -set_serial $serial \
97- -out truststore/client/$client .crt \
121+ -out truststore/client/$cert_name .crt \
98122 -days 36500 \
99123 -sha256 \
100124 -extfile /tmp/client.conf \
101125 -extensions v3_req \
102126 -extensions usr_cert & > /dev/null
103127
104- cat truststore/client/$client .crt truststore/ca/$ca .crt > truststore/server/$client .pem
105-
106- echo -e " ✅ Successfully generated $client keypair"
128+ echo -e " ✅ Successfully generated $cert_name keypair for ca=$ca_name (FQDN: $fqdn )"
107129 rm /tmp/client.conf
130+ rm truststore/client/$cert_name .csr
131+ return 0
132+ }
133+
134+ # Rotate a CA - archive the existing CA and build a new one
135+ # The previous CA is added to the new CA bundle (can be removed after clients have updated)
136+ function _truststore_rotate_ca() {
137+ if [[ $# -ne 2 ]]; then
138+ echo " Usage: $0 rotate-ca <env> <fqdn>"
139+ return 1;
140+ fi
141+
142+ env=" $1 "
143+ fqdn=" $2 "
144+
145+ # Archive the existing ca certs
146+ archive_date=" $( date +%Y-%m-%d) "
147+ if [[ -f " truststore/ca/$env .archived_$archive_date .crt" ]] ||
148+ [[ -f " truststore/ca/$env .archived_$archive_date .key" ]] ||
149+ [[ -f " truststore/server/$env .archived_$archive_date .pem" ]]; then
150+ echo " Error: Archive files already exist for date $archive_date - please resolve before rotating the CA" 1>&2
151+ return 1
152+ fi
153+
154+ mv " truststore/ca/$env .crt" " truststore/ca/$env .archived_$archive_date .crt"
155+ mv " truststore/ca/$env .key" " truststore/ca/$env .archived_$archive_date .key"
156+ mv " truststore/server/$env .pem" " truststore/server/$env .archived_$archive_date .pem"
157+
158+ # Build a new CA
159+ _truststore_build_ca " $env " " $fqdn "
160+
161+ # Add the previous CA to the new CA bundle (can be removed after clients have updated)
162+ cat " truststore/ca/$env .archived_$archive_date .crt" >> " truststore/server/$env .pem"
163+
164+ echo -e " ✅ Successfully rotated CA for $env - previous CA archived with date: $archive_date "
165+ return 0
108166}
109167
168+ # Rotate a client cert - archive the existing cert and build a new one
169+ function _truststore_rotate_cert() {
170+ if [[ $# -ne 3 ]]; then
171+ echo " Usage: $0 rotate-cert <env> <ca> <fqdn>"
172+ exit 1;
173+ fi
174+
175+ cert_name=" $1 "
176+ ca_name=" $2 "
177+ fqdn=" $3 "
178+
179+ # Archive the existing client certs
180+ archive_date=$( date +%Y-%m-%d)
181+ if [[ -f " truststore/client/$cert_name .archived_$archive_date .crt" ]] ||
182+ [[ -f " truststore/client/$cert_name .archived_$archive_date .key" ]]; then
183+ echo " Error: Archive files already exist for date $archive_date - please resolve before rotating the client cert" 1>&2
184+ return 1
185+ fi
186+
187+ mv " truststore/client/$cert_name .crt" " truststore/client/$cert_name .archived_$archive_date .crt"
188+ mv " truststore/client/$cert_name .key" " truststore/client/$cert_name .archived_$archive_date .key"
189+
190+ # Build a new client cert
191+ _truststore_build_cert " $cert_name " " $ca_name " " $fqdn "
192+
193+ echo -e " ✅ Successfully rotated client cert for $cert_name - previous cert archived with date: $archive_date "
194+ return 0
195+ }
196+
197+ # Disable an archived CA by removing it from the server pem file
198+ function _disable_archived_ca() {
199+ env=" $1 "
200+ cat " truststore/ca/$env .crt" > " truststore/server/$env .pem"
201+
202+ echo -e " ✅ Successfully disabled archived CA for $env "
203+ return 0
204+ }
205+
206+ # Restore an archived CA by moving the archived files back to their original names
207+ function _restore_archived_ca() {
208+ env=" $1 "
209+ archive_date=" $2 "
210+
211+ mv " truststore/ca/$env .archived_$archive_date .crt" " truststore/ca/$env .crt"
212+ mv " truststore/ca/$env .archived_$archive_date .key" " truststore/ca/$env .key"
213+ mv " truststore/server/$env .archived_$archive_date .pem" " truststore/server/$env .pem"
214+
215+ echo -e " ✅ Successfully restored archived CA for $env from date: $archive_date "
216+ return 0
217+ }
218+
219+ # Restore an archived client cert by moving the archived files back to their original names
220+ function _restore_archived_cert() {
221+ env=" $1 "
222+ archive_date=" $2 "
223+
224+ mv " truststore/client/$env .archived_$archive_date .crt" " truststore/client/$env .crt"
225+ mv " truststore/client/$env .archived_$archive_date .key" " truststore/client/$env .key"
226+
227+ echo -e " ✅ Successfully restored archived client cert for $env from date: $archive_date "
228+ return 0
229+ }
110230
111231function _truststore_build_all() {
112- _truststore_build_ca " prod" " record-locator.national.nhs.uk "
113- _truststore_build_ca " int" " record-locator.int.national.nhs.uk "
114- _truststore_build_ca " ref" " record-locator.ref.national.nhs.uk "
115- _truststore_build_ca " qa" " qa.record-locator.national.nhs.uk "
116- _truststore_build_ca " dev" " record-locator.dev.national.nhs.uk "
232+ _truststore_build_ca " prod" " record-locator.national.nhs.uk_CA2 "
233+ _truststore_build_ca " int" " record-locator.int.national.nhs.uk_CA2 "
234+ _truststore_build_ca " ref" " record-locator.ref.national.nhs.uk_CA2 "
235+ _truststore_build_ca " qa" " qa.record-locator.national.nhs.uk_CA2 "
236+ _truststore_build_ca " dev" " record-locator.dev.national.nhs.uk_CA2 "
117237
118238 _truststore_build_cert " prod" " prod" " api.record-locator.national.nhs.uk"
119239 _truststore_build_cert " int" " int" " int.api.record-locator.int.national.nhs.uk"
120240 _truststore_build_cert " ref" " ref" " ref.api.record-locator.ref.national.nhs.uk"
121241 _truststore_build_cert " qa" " qa" " api.qa.record-locator.national.nhs.uk"
122242 _truststore_build_cert " dev" " dev" " dev.api.record-locator.dev.national.nhs.uk"
243+
244+ echo -e " ✅ Successfully built all truststore files"
245+ return 0
123246}
124247
125248function _truststore_pull_ca() {
126249 env=$1
250+ echo " Pulling ${env} ca certificate"
127251 aws s3 cp " s3://${BUCKET} /ca/${env} .crt" " truststore/ca/${env} .crt"
252+
253+ echo -e " ✅ Successfully pulled ${env} ca certificate from s3://${BUCKET} "
254+ return 0
255+ }
256+
257+ function _truststore_pull_ca_key() {
258+ env=$1
259+ echo " Pulling ${env} ca private key"
260+ aws s3 cp " s3://${BUCKET} /ca/${env} .key" " truststore/ca/${env} .key"
261+
262+ echo -e " ✅ Successfully pulled ${env} ca private key from s3://${BUCKET} "
263+ return 0
128264}
129265
130266function _truststore_pull_client() {
131267 env=$1
268+ echo " Pulling ${env} client certificate"
132269 aws s3 cp " s3://${BUCKET} /client/${env} .key" " truststore/client/${env} .key"
133270 aws s3 cp " s3://${BUCKET} /client/${env} .crt" " truststore/client/${env} .crt"
271+
272+ echo -e " ✅ Successfully pulled ${env} client truststore files from s3://${BUCKET} "
273+ return 0
134274}
135275
136276function _truststore_pull_server() {
137277 env=$1
138- echo " Pulling truststore/server/ ${env} .pem "
278+ echo " Pulling ${env} server trust certificate "
139279 aws s3 cp " s3://${BUCKET} /server/${env} .pem" " truststore/server/${env} .pem"
280+
281+ echo -e " ✅ Successfully pulled ${env} server truststore files from s3://${BUCKET} "
282+ return 0
140283}
141284
142285function _truststore_pull_all() {
143286 env=$1
144287 _truststore_pull_ca $env
145288 _truststore_pull_client $env
146289 _truststore_pull_server $env
290+
291+ echo -e " ✅ Successfully pulled all ${env} truststore files from s3://${BUCKET} "
292+ return 0
293+ }
294+
295+ function _truststore_push_all() {
296+ env=$1
297+
298+ timestamp=" $( date +%Y-%m-%d_%H%M%S) "
299+ backup_dir=" truststore/backup/${env} _${timestamp} "
300+
301+ echo -e " Backing up existing files to ${backup_dir} ...."
302+ mkdir -p " ${backup_dir} /ca" " ${backup_dir} /client" " ${backup_dir} /server"
303+ for f in " ca/${env} .crt" " ca/${env} .key" " client/${env} .crt" " client/${env} .key" " server/${env} .pem"
304+ do
305+ echo
306+ aws s3 cp " s3://${BUCKET} /$f " " ${backup_dir} /$f " || echo " No existing file s3://${BUCKET} /$f to back up"
307+
308+ if [[ -f " ${backup_dir} /$f " ]]
309+ then
310+ diff --brief " truststore/$f " " ${backup_dir} /$f " || true
311+ fi
312+ done
313+
314+ echo
315+ echo -n " WARNING: You are about to upload files to the ${env} truststore - are you sure? [yes/NO] "
316+ read answer
317+ if [[ " $answer " != " yes" ]]; then
318+ echo " Aborting upload to ${env} truststore" 1>&2
319+ return 1
320+ fi
321+
322+ echo " Uploading ${env} ca certificate"
323+ aws s3 cp " truststore/ca/${env} .crt" " s3://${BUCKET} /ca/${env} .crt"
324+
325+ echo " Uploading ${env} ca private key"
326+ aws s3 cp " truststore/ca/${env} .key" " s3://${BUCKET} /ca/${env} .key"
327+
328+ echo " Uploading ${env} client certificate"
329+ aws s3 cp " truststore/client/${env} .key" " s3://${BUCKET} /client/${env} .key"
330+ aws s3 cp " truststore/client/${env} .crt" " s3://${BUCKET} /client/${env} .crt"
331+
332+ echo " Uploading ${env} server trust certificate"
333+ aws s3 cp " truststore/server/${env} .pem" " s3://${BUCKET} /server/${env} .pem"
334+
335+ echo -e " ✅ Successfully uploaded all ${env} truststore files to s3://${BUCKET} "
336+ return 0
147337}
148338
149339function _truststore() {
@@ -157,7 +347,14 @@ function _truststore() {
157347 " pull-all" ) _truststore_pull_all $args ;;
158348 " pull-server" ) _truststore_pull_server $args ;;
159349 " pull-client" ) _truststore_pull_client $args ;;
160- " pull-ca" ) _truststore_pull_server $args ;;
350+ " pull-ca" ) _truststore_pull_ca $args ;;
351+ " pull-ca-key" ) _truststore_pull_ca_key $args ;;
352+ " push-all" ) _truststore_push_all $args ;;
353+ " rotate-ca" ) _truststore_rotate_ca $args ;;
354+ " rotate-cert" ) _truststore_rotate_cert $args ;;
355+ " disable-archived-ca" ) _disable_archived_ca $args ;;
356+ " restore-archived-ca" ) _restore_archived_ca $args ;;
357+ " restore-archived-cert" ) _restore_archived_cert $args ;;
161358 * ) _truststore_help $args ;;
162359 esac
163360}
0 commit comments