corosync-qdevice-net-certutil.sh 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. #!@BASHPATH@
  2. #
  3. # Copyright (c) 2015-2026 Red Hat, Inc.
  4. #
  5. # All rights reserved.
  6. #
  7. # Author: Jan Friesse (jfriesse@redhat.com)
  8. #
  9. # This software licensed under BSD license, the text of which follows:
  10. #
  11. # Redistribution and use in source and binary forms, with or without
  12. # modification, are permitted provided that the following conditions are met:
  13. #
  14. # - Redistributions of source code must retain the above copyright notice,
  15. # this list of conditions and the following disclaimer.
  16. # - Redistributions in binary form must reproduce the above copyright notice,
  17. # this list of conditions and the following disclaimer in the documentation
  18. # and/or other materials provided with the distribution.
  19. # - Neither the name of the Red Hat, Inc. nor the names of its
  20. # contributors may be used to endorse or promote products derived from this
  21. # software without specific prior written permission.
  22. #
  23. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  24. # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  25. # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  26. # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  27. # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  28. # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  29. # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  30. # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  31. # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  32. # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
  33. # THE POSSIBILITY OF SUCH DAMAGE.
  34. #
  35. BASE_DIR="@COROSYSCONFDIR@/qdevice/net"
  36. DB_DIR_QNETD="@COROSYSCONFDIR@/qnetd/nssdb"
  37. DB_DIR="$BASE_DIR/nssdb"
  38. CA_NICKNAME="QNet CA"
  39. CLUSTER_NICKNAME="Cluster Cert"
  40. PWD_FILE="$DB_DIR/pwdfile.txt"
  41. NOISE_FILE="$DB_DIR/noise.txt"
  42. CA_EXPORT_BASE="qnetd-cacert.crt"
  43. CA_EXPORT_FILE="$DB_DIR_QNETD/$CA_EXPORT_BASE"
  44. CRQ_FILE_BASE="qdevice-net-node.crq"
  45. CRQ_FILE="$DB_DIR/$CRQ_FILE_BASE"
  46. P12_FILE_BASE="qdevice-net-node.p12"
  47. P12_FILE="$DB_DIR/$P12_FILE_BASE"
  48. QNETD_CERTUTIL_CMD="corosync-qnetd-certutil"
  49. CERTDB_FILES=("cert9.db key4.db pkcs11.txt"
  50. "cert8.db key3.db secmod.db")
  51. REMOTE_SHELL_EXECUTABLE="ssh"
  52. REMOTE_COPY_EXECUTABLE="scp"
  53. # errx exit_code message
  54. errx() {
  55. echo "$2" >&2
  56. exit "$1"
  57. }
  58. usage() {
  59. echo "$0: [-i|-M|-m|-Q|-r] [-C scp_command] [-c certificate] [-g keysize] [-k keytype] [-n cluster_name] [-S ssh_command] [-q paramset]"
  60. echo
  61. echo " -i Initialize node CA. Needs CA certificate from server"
  62. echo " -M Import signed cluster certificate and export certificate with key to pk12 file"
  63. echo " -m Import cluster certificate on node (needs pk12 certificate)"
  64. echo " -Q Quick start. Uses ssh/scp to initialze both qnetd and nodes."
  65. echo " -r Generate cluster certificate request"
  66. echo ""
  67. echo " -C scp_command Alternative remote copy command to be use in place of scp. If not specified, scp is used."
  68. echo " -c certificate Ether CA, CRQ, CRT or pk12 certificate (operation dependant)"
  69. echo " -g keysize Key size in bits - passed directly to certutil as -g parameter"
  70. echo " -k keytype Type of key - passed directly to certutil as -k parameter"
  71. echo " -n cluster_name Name of cluster (for -r and -s operations)"
  72. echo " -S ssh_command Alternative remote shell command to be use in place of ssh. If not specified, ssh is used."
  73. echo " -q paramset Parameter set (curve name, ml-dsa set) - passed directly to certutil as -q parameter"
  74. echo ""
  75. echo "Typical usage:"
  76. echo "- Initialize database on QNetd server by running $QNETD_CERTUTIL_CMD -i"
  77. echo "- Copy exported QNetd CA certificate ($CA_EXPORT_FILE) to every node"
  78. echo "- On one of cluster node initialize database by running $0 -i -c $CA_EXPORT_BASE"
  79. echo "- Generate certificate request: $0 -r -n Cluster (Cluster name must match cluster_name key in the corosync.conf)"
  80. echo "- Copy exported CRQ to QNetd server"
  81. echo "- On QNetd server sign and export cluster certificate by running $QNETD_CERTUTIL_CMD -s -c $CRQ_FILE_BASE -n Cluster"
  82. echo "- Copy exported CRT to node where certificate request was created"
  83. echo "- Import certificate on node where certificate request was created by running $0 -M -c cluster-Cluster.crt"
  84. echo "- Copy output $P12_FILE_BASE to all other cluster nodes"
  85. echo "- On all other nodes in cluster:"
  86. echo " - Init database by running $0 -i -c $CA_EXPORT_BASE"
  87. echo " - Import cluster certificate and key: $0 -m -c $P12_FILE_BASE"
  88. echo ""
  89. echo "It is also possible to use Quick start (-Q). This needs properly configured remote shell command and remote copy command (ssh and scp by default)."
  90. echo " $0 -Q [-S ssh_command] [-C scp_command] -n Cluster qnetd_server node1 node2 ... nodeN"
  91. exit 0
  92. }
  93. get_certutil_key_params() {
  94. CERTUTIL_PARAMS=""
  95. if [ ! -z "$COROSYNC_QDEVICE_NET_CERTUTIL_KEY_SIZE" ];then
  96. CERTUTIL_PARAMS="$CERTUTIL_PARAMS -g $COROSYNC_QDEVICE_NET_CERTUTIL_KEY_SIZE"
  97. fi
  98. if [ ! -z "$COROSYNC_QDEVICE_NET_CERTUTIL_KEY_TYPE" ];then
  99. CERTUTIL_PARAMS="$CERTUTIL_PARAMS -k $COROSYNC_QDEVICE_NET_CERTUTIL_KEY_TYPE"
  100. fi
  101. if [ ! -z "$COROSYNC_QDEVICE_NET_CERTUTIL_PARAMSET" ];then
  102. CERTUTIL_PARAMS="$CERTUTIL_PARAMS -q $COROSYNC_QDEVICE_NET_CERTUTIL_PARAMSET"
  103. fi
  104. echo "$CERTUTIL_PARAMS"
  105. }
  106. create_new_noise_file() {
  107. local noise_file="$1"
  108. if [ ! -e "$noise_file" ];then
  109. echo "Creating new noise file $noise_file"
  110. (ps -elf; date; w) | sha1sum | (read sha_sum rest; echo $sha_sum) > "$noise_file"
  111. chown root:root "$noise_file"
  112. chmod 0660 "$noise_file"
  113. else
  114. echo "Using existing noise file $noise_file"
  115. fi
  116. }
  117. find_certdb_files() {
  118. for cert_files_index in "${!CERTDB_FILES[@]}";do
  119. cert_files=${CERTDB_FILES[$cert_files_index]}
  120. test_file=${cert_files%% *}
  121. if [ -f "$DB_DIR/$test_file" ];then
  122. echo "$cert_files"
  123. return 0
  124. fi
  125. done
  126. }
  127. init_node_ca() {
  128. cert_files=`find_certdb_files`
  129. if [ "$cert_files" != "" ];then
  130. errx 1 "Certificate database already exists. Delete it to continue"
  131. fi
  132. if ! [ -d "$DB_DIR" ];then
  133. echo "Creating $DB_DIR"
  134. mkdir -p "$DB_DIR"
  135. chown root:root "$DB_DIR"
  136. chmod 0770 "$DB_DIR"
  137. fi
  138. echo "Creating new key and cert db"
  139. echo -n "" > "$PWD_FILE"
  140. chown root:root "$PWD_FILE"
  141. chmod 0660 "$PWD_FILE"
  142. certutil -N -d "$DB_DIR" -f "$PWD_FILE"
  143. cert_files=`find_certdb_files`
  144. if [ "$cert_files" == "" ];then
  145. errx 1 "Can't find certificate database files. Certificate database ($DB_DIR) cannot be created"
  146. fi
  147. for fname in $cert_files;do
  148. chown root:root "$DB_DIR/$fname"
  149. chmod 0660 "$DB_DIR/$fname"
  150. done
  151. create_new_noise_file "$NOISE_FILE"
  152. echo "Importing CA"
  153. certutil -d "$DB_DIR" -A -t "CT,c,c" -n "$CA_NICKNAME" -f "$PWD_FILE" \
  154. -i "$CERTIFICATE_FILE"
  155. }
  156. gen_cluster_cert_req() {
  157. cert_files=`find_certdb_files`
  158. if [ "$cert_files" == "" ];then
  159. errx 1 "Certificate database doesn't exists. Use $0 -i to create it"
  160. fi
  161. echo "Creating new certificate request"
  162. certutil -R -s "CN=$CLUSTER_NAME" -o "$CRQ_FILE" -d "$DB_DIR" -f "$PWD_FILE" -z "$NOISE_FILE" \
  163. $(get_certutil_key_params)
  164. echo "Certificate request stored in $CRQ_FILE"
  165. }
  166. import_signed_cert() {
  167. cert_files=`find_certdb_files`
  168. if [ "$cert_files" == "" ];then
  169. errx 1 "Certificate database doesn't exists. Use $0 -i to create it"
  170. fi
  171. echo "Importing signed cluster certificate"
  172. certutil -d "$DB_DIR" -A -t "u,u,u" -n "$CLUSTER_NICKNAME" -i "$CERTIFICATE_FILE"
  173. pk12util -d "$DB_DIR" -o "$P12_FILE" -W "" -n "$CLUSTER_NICKNAME"
  174. echo "Certificate stored in $P12_FILE"
  175. }
  176. import_pk12() {
  177. cert_files=`find_certdb_files`
  178. if [ "$cert_files" == "" ];then
  179. errx 1 "Certificate database doesn't exists. Use $0 -i to create it"
  180. fi
  181. echo "Importing cluster certificate and key"
  182. pk12util -i "$CERTIFICATE_FILE" -d "$DB_DIR" -W ""
  183. }
  184. # Wrapper on top of scp which first copies (scp) file to local machine saving to
  185. # temporary file and then copies to another remote machine. Standard scp doesn't
  186. # handle situation with two hosts in one command very well when agent forwarding
  187. # is used and there is no key between two machines.
  188. remote_scp() {
  189. tmp_file=`mktemp`
  190. $REMOTE_COPY_EXECUTABLE "$1" "$tmp_file"
  191. $REMOTE_COPY_EXECUTABLE "$tmp_file" "$2"
  192. rm -f "$tmp_file"
  193. }
  194. quick_start() {
  195. qnetd_addr="$1"
  196. master_node="$2"
  197. other_nodes="$3"
  198. # Sanity check
  199. for i in "$master_node" $other_nodes;do
  200. if $REMOTE_SHELL_EXECUTABLE root@$i "[ -d \"$DB_DIR\" ]";then
  201. errx 1 "Node $i seems to be already initialized. Please delete $DB_DIR"
  202. fi
  203. if ! $REMOTE_SHELL_EXECUTABLE "root@$i" "$0" > /dev/null;then
  204. errx 1 "Node $i doesn't have $0 installed"
  205. fi
  206. done
  207. # Initialize qnetd server (it's no problem if server is already initialized)
  208. $REMOTE_SHELL_EXECUTABLE "root@$qnetd_addr" "$QNETD_CERTUTIL_CMD -i" || true
  209. # Copy CA cert to all nodes and initialize them
  210. for node in "$master_node" $other_nodes;do
  211. remote_scp "root@$qnetd_addr:$CA_EXPORT_FILE" "root@$node:/tmp/$CA_EXPORT_BASE"
  212. $REMOTE_SHELL_EXECUTABLE "root@$node" "$0 -i -c \"/tmp/$CA_EXPORT_BASE\" && rm /tmp/$CA_EXPORT_BASE"
  213. done
  214. # Generate cert request
  215. $REMOTE_SHELL_EXECUTABLE "root@$master_node" "$0 -r -n \"$CLUSTER_NAME\""
  216. # Copy exported cert request to qnetd server
  217. remote_scp "root@$master_node:$CRQ_FILE" "root@$qnetd_addr:/tmp/$CRQ_FILE_BASE"
  218. # Sign and export cluster certificate
  219. $REMOTE_SHELL_EXECUTABLE "root@$qnetd_addr" "$QNETD_CERTUTIL_CMD -s -c \"/tmp/$CRQ_FILE_BASE\" -n \"$CLUSTER_NAME\""
  220. # Copy exported CRT to master node
  221. remote_scp "root@$qnetd_addr:$DB_DIR_QNETD/cluster-$CLUSTER_NAME.crt" \
  222. "root@$master_node:$DB_DIR/cluster-$CLUSTER_NAME.crt"
  223. # Import certificate
  224. $REMOTE_SHELL_EXECUTABLE "root@$master_node" "$0 -M -c \"$DB_DIR/cluster-$CLUSTER_NAME.crt\""
  225. # Copy pk12 cert to all nodes and import it
  226. for node in $other_nodes;do
  227. remote_scp "root@$master_node:$P12_FILE" "$node:$P12_FILE"
  228. # Ignore import errors (no easy way to remove "improperly formatted DER-encoded" message
  229. # resulting in error code != 0 but successful import)
  230. $REMOTE_SHELL_EXECUTABLE "root@$node" "$0 -m -c \"$P12_FILE\"" || true
  231. done
  232. }
  233. # Initialize options that may be overwritten by the configuration file
  234. COROSYNC_QDEVICE_NET_CERTUTIL_KEY_SIZE=""
  235. COROSYNC_QDEVICE_NET_CERTUTIL_KEY_TYPE=""
  236. COROSYNC_QDEVICE_NET_CERTUTIL_PARAMSET=""
  237. # Import configuration file if it exists
  238. if [ -f "@INITCONFIGDIR@/corosync-qdevice" ];then
  239. . "@INITCONFIGDIR@/corosync-qdevice"
  240. fi
  241. # Strict mode
  242. set -euo pipefail
  243. trap 's=$?; echo >&2 "$0: Error on line "$LINENO": $BASH_COMMAND"; exit $s' ERR
  244. OPERATION=""
  245. CERTIFICATE_FILE=""
  246. CLUSTER_NAME=""
  247. while getopts ":hiMmQrC:c:g:k:n:S:q:" opt; do
  248. case $opt in
  249. i)
  250. OPERATION=init_node_ca
  251. ;;
  252. M)
  253. OPERATION=import_signed_cert
  254. ;;
  255. m)
  256. OPERATION=import_pk12
  257. ;;
  258. Q)
  259. OPERATION=quick_start
  260. ;;
  261. r)
  262. OPERATION=gen_cluster_cert_req
  263. ;;
  264. h)
  265. usage
  266. ;;
  267. C)
  268. REMOTE_COPY_EXECUTABLE="$OPTARG"
  269. ;;
  270. c)
  271. CERTIFICATE_FILE="$OPTARG"
  272. ;;
  273. g)
  274. COROSYNC_QDEVICE_NET_CERTUTIL_KEY_SIZE="$OPTARG"
  275. ;;
  276. k)
  277. COROSYNC_QDEVICE_NET_CERTUTIL_KEY_TYPE="$OPTARG"
  278. ;;
  279. n)
  280. CLUSTER_NAME="$OPTARG"
  281. ;;
  282. S)
  283. REMOTE_SHELL_EXECUTABLE="$OPTARG"
  284. ;;
  285. q)
  286. COROSYNC_QDEVICE_NET_CERTUTIL_PARAMSET="$OPTARG"
  287. ;;
  288. \?)
  289. errx 1 "Invalid option: -$OPTARG"
  290. ;;
  291. :)
  292. errx 1 "Option -$OPTARG requires an argument."
  293. ;;
  294. esac
  295. done
  296. [ "$OPERATION" == "" ] && usage
  297. case "$OPERATION" in
  298. "init_node_ca")
  299. if ! [ -e "$CERTIFICATE_FILE" ];then
  300. errx 2 "Can't open certificate file $CERTIFICATE_FILE"
  301. fi
  302. init_node_ca
  303. ;;
  304. "gen_cluster_cert_req")
  305. if [ "$CLUSTER_NAME" == "" ];then
  306. errx 2 "You have to specify cluster name"
  307. fi
  308. gen_cluster_cert_req
  309. ;;
  310. "import_signed_cert")
  311. if ! [ -e "$CERTIFICATE_FILE" ];then
  312. errx 2 "Can't open certificate file $CERTIFICATE_FILE"
  313. fi
  314. import_signed_cert
  315. ;;
  316. "import_pk12")
  317. if ! [ -e "$CERTIFICATE_FILE" ];then
  318. errx 2 "Can't open certificate file $CERTIFICATE_FILE"
  319. fi
  320. import_pk12
  321. ;;
  322. "quick_start")
  323. shift $((OPTIND-1))
  324. if [ "$CLUSTER_NAME" == "" ];then
  325. errx 2 "You have to specify cluster name"
  326. fi
  327. qnetd_addr=${1:-}
  328. if [ "$qnetd_addr" == "" ];then
  329. errx 2 "No QNetd server address provided."
  330. fi
  331. shift 1
  332. master_node=${1:-}
  333. if [ "$master_node" == "" ];then
  334. errx 2 "No nodes provided."
  335. fi
  336. shift 1
  337. other_nodes="$*"
  338. quick_start "$qnetd_addr" "$master_node" "$other_nodes"
  339. ;;
  340. *)
  341. usage
  342. ;;
  343. esac