From 228a1400948740f9c1e1d9948dc0b74683e3eb3a Mon Sep 17 00:00:00 2001 From: Eyal Kalderon Date: Wed, 14 Oct 2026 00:38:33 -0400 Subject: [PATCH] Add deploy hook for Redfish-compatible BMCs (#7276) * Add `deploy/redfish.sh` and implement Redfish session log in/out Since `acme.sh` intends to keep third-party dependencies to a minimum, this implementation will not rely on `redfishtool`. * Detect BMC manager and replace existing cert with ACME cert * Reuse `_toPkcs8` defined in `acme.sh` * Return early if unsupported key algorithms or bit lengths are used * Improve robustness and UX a bit * Support opt-in automatic restart of BMC * Improve log output and inline docs some more * Remove `test-redfish-deploy-hook.sh` test harness The business logic has been implemented, so it should be safe to delete this test harness now and just interact with `acme.sh` directly. * Trim `_redfish_` prefixes from local variables Since each deploy hook is executed within its own subshell, there's no risk of polluting the outer `acme.sh` environment nor any other deploy hooks that might follow. * Save deploy configuration changes to disk * Tweak deploy hook log messages a bit * Add `DEPLOY_REDFISH_MANAGER` environment variable * Add function for checking HTTP status code * Add more helper functions, clean up log output a bit more * Return early from non-zero `curl` exit codes * Only upload leaf cert in `--sign-csr` mode * Fix bug with RSA key compatibility error message * Remove `_redfish_load_deploy_conf` helper, use consistent var naming * Add several more helper functions * Check whether server supports Certificate schema >=1.4.0 Redfish servers with a schema version older than this don't support uploading private keys + full certificate chains directly. Instead, the CSR must be generated on the server and signed with `acme.sh --sign-csr` prior to deployment. * Clarify environment variable naming a bit * Don't implicitly opt into `HTTPS_INSECURE` * Feedback: Fix lower-bound RSA key length check * Feedback: Verify file existence with `-f` flag * Feedback: Correctly map `TPM_ECC_CURVE` key length values * Feedback: Replace exact HTTP 204 checks with 2xx instead * Feedback: Use POSIX-compliant alternative to `\+` * Feedback: Get session token from body instead of HTTP header * Feedback: Save PKCS#8 private key directly to variable * Feedback: Drop `jq` dependency, handle missing action info * Fix regression in `_redfish_rest()` error reporting * Simplify construction of expected certificate JSON string Confirmed with a real-world test that the value of `_certificate_str` is unchanged before and after applying this commit. See below: diff --git a/deploy/redfish.sh b/deploy/redfish.sh index e485fd60..3459b71a 100644 --- a/deploy/redfish.sh +++ b/deploy/redfish.sh @@ -131,6 +131,10 @@ redfish_deploy() { printf '%s\n' "${_ckey_pkcs8}" cat "${_cfullchain}" } | _json_encode)" + + _new_certificate_str="$(echo "${_ckey_pkcs8}" | cat - "${_cfullchain}" | _json_encode)" + [ "${_certificate_str}" = "${_new_certificate_str}" ] || return 123 + _certificate_type='PEMchain' else _info "Private key and full certificate chain not available; we must be using a server-generated CSR." * Include link to `redfish-generate-csr.sh` helper script in error message * Feedback: Drop use of `paste` and `expr` * Feedback: Remove stale `_err` invocation * Feedback: Correctly deal with whitespace after colon * Feedback: Handle `\n` in username and password in Dash shell It seems that `echo` in Dash automatically converts `\n` into a line break, causing usernames and/or passwords containing a literal backslash-n to reach the BMC with a newline, breaking login. It seems that `printf '%s'` doesn't have the same issue, so we use those to format `_username` and `_password` instead of `echo`. * Feedback: Link to acme.sh wiki instead of Gist * Feedback: Fix `Certificate` schema version check * Feedback: Better handle missing key algo/length/curve info This commit changes how the deploy hook detects which cryptographic algorithms are supported by the server if not explicitly declared in `KeyPairAlgorithm`. Instead of blindly assuming RSA, we instead check for the presence of `KeyBitLength` and/or `KeyCurveId` fields and try to make an educated guess. If not enough info exists to determine key compatibility, we fail-open instead of failing closed; the server will yell at us anyway if `ReplaceCertificate` doesn't support something. * Feedback: Fix character escaping of username/password * Feedback: Correctly parse inline `KeyBitLength@Redfish.AllowableValues` This metadatum is represented as a JSON array of allowed values, not a numeric range with min/max. Make sure to treat the element in the list as the maximum, and not assume the list is at most 2 elements long. * Feedback: Add "\n" to format string for Solaris compatibility * Feedback: Use POSIX parameter expansion instead of `rev` --- deploy/redfish.sh | 492 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 492 insertions(+) create mode 100644 deploy/redfish.sh diff --git a/deploy/redfish.sh b/deploy/redfish.sh new file mode 100644 index 00000000..ab65b8cd --- /dev/null +++ b/deploy/redfish.sh @@ -0,0 +1,492 @@ +#!/usr/bin/env sh + +# Script to deploy certificates to compatible BMCs (baseboard management +# controllers) using the [Redfish API](https://www.dmtf.org/standards/redfish). +# +# Environment Variables: +# +# ```sh +# DEPLOY_REDFISH_HOST="ipmi.example.com" # Required +# DEPLOY_REDFISH_USERNAME="Administrator" # Required +# DEPLOY_REDFISH_PASSWORD="superuser" # Required +# DEPLOY_REDFISH_USE_BASIC_AUTH=0 # Optional (set to "1" to enable) +# DEPLOY_REDFISH_RESTART=0 # Optional (set to "1" to enable) +# DEPLOY_REDFISH_MANAGER= # Optional (e.g. "/redfish/v1/Managers/Self") +# DEPLOY_REDFISH_CERTIFICATE= # Optional (e.g. "/redfish/v1/CertificateService/Certificates/1") +# ``` +# +# Compatibility: +# +# Compatible with any IPMI/BMC solution supporting Redfish v1.6.1 or newer (or +# Redfish Schema Bundle 2018.3), as defined in the following documents: +# +# +# +# +# +# This script is confirmed to work on the following systems: +# +# | IPMI Solution | BMC Chipset | Firmware Family | Redfish Version | +# |------------------|----------------|------------------|-----------------| +# | ASUS ASMB12-iKVM | ASPEED AST2600 | AMI MegaRAC SP-X | 1.15.1 | +# +# However, this should also work with HPE iLO 5 v2.42 (or newer), Dell iDRAC9 +# v5.0 (or newer), or any ASUS/Lenovo/Supermicro IPMI solution based on AMI +# MegaRAC SP-X v12.x (or newer). + +#domain keyfile certfile cafile fullchain +redfish_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + _getdeployconf DEPLOY_REDFISH_HOST + _getdeployconf DEPLOY_REDFISH_USERNAME + _getdeployconf DEPLOY_REDFISH_PASSWORD + _getdeployconf DEPLOY_REDFISH_USE_BASIC_AUTH + _getdeployconf DEPLOY_REDFISH_RESTART + _getdeployconf DEPLOY_REDFISH_MANAGER + _getdeployconf DEPLOY_REDFISH_CERTIFICATE + + if [ -z "${DEPLOY_REDFISH_HOST}" ]; then + _err 'DEPLOY_REDFISH_HOST must be set. Please specify the IP or domain name of the Redfish server.' + return 1 + fi + + if [ -z "${DEPLOY_REDFISH_USERNAME}" ] || [ -z "${DEPLOY_REDFISH_PASSWORD}" ]; then + _err 'DEPLOY_REDFISH_USERNAME and DEPLOY_REDFISH_PASSWORD must be set.' + return 1 + fi + + DEPLOY_REDFISH_USE_BASIC_AUTH="${DEPLOY_REDFISH_USE_BASIC_AUTH:-0}" + DEPLOY_REDFISH_RESTART="${DEPLOY_REDFISH_RESTART:-0}" + + _debug DEPLOY_REDFISH_HOST "${DEPLOY_REDFISH_HOST}" + _debug DEPLOY_REDFISH_USERNAME "${DEPLOY_REDFISH_USERNAME}" + _secure_debug DEPLOY_REDFISH_PASSWORD "${DEPLOY_REDFISH_PASSWORD}" + _debug DEPLOY_REDFISH_USE_BASIC_AUTH "${DEPLOY_REDFISH_USE_BASIC_AUTH}" + + _redfish_log_in "$DEPLOY_REDFISH_USERNAME" "$DEPLOY_REDFISH_PASSWORD" || return 1 + + # Credentials are proven correct now -- save them, rather than only at the + # very end, so a later step failing doesn't discard a working login. + _savedeployconf DEPLOY_REDFISH_HOST "${DEPLOY_REDFISH_HOST}" + _savedeployconf DEPLOY_REDFISH_USERNAME "${DEPLOY_REDFISH_USERNAME}" + _savedeployconf DEPLOY_REDFISH_PASSWORD "${DEPLOY_REDFISH_PASSWORD}" 'base64' + _savedeployconf DEPLOY_REDFISH_USE_BASIC_AUTH "${DEPLOY_REDFISH_USE_BASIC_AUTH}" + + if ! _redfish_get_certificate_service_endpoint; then + _err "Redfish server ${DEPLOY_REDFISH_HOST} doesn't support certificate management API." + return 1 + fi + + _debug DEPLOY_REDFISH_RESTART "${DEPLOY_REDFISH_RESTART}" + _debug DEPLOY_REDFISH_MANAGER "${DEPLOY_REDFISH_MANAGER}" + _debug DEPLOY_REDFISH_CERTIFICATE "${DEPLOY_REDFISH_CERTIFICATE}" + + if [ -n "${DEPLOY_REDFISH_MANAGER}" ]; then + _manager_endpoint="${DEPLOY_REDFISH_MANAGER}" + else + _info 'Identifying REST endpoint of primary Redfish manager.' + _redfish_get_primary_manager_endpoint || return 1 + _savedeployconf DEPLOY_REDFISH_MANAGER "${_manager_endpoint}" + fi + + if [ -n "${DEPLOY_REDFISH_CERTIFICATE}" ]; then + _certificate_endpoint="${DEPLOY_REDFISH_CERTIFICATE}" + else + _info "Identifying REST endpoint of primary TLS certificate on: ${_manager_endpoint}" + _redfish_get_manager_certificate_endpoint "${_manager_endpoint}" || return 1 + fi + + if [ -f "${_ckey}" ] && [ -f "${_cfullchain}" ]; then + _info "Uploading private key and full certificate chain to: ${_certificate_endpoint}" + + if ! _redfish_supports_full_certificate_chains; then + _err 'Redfish server does not support uploading full certificate chains!' + _err "Please call 'GenerateCSR' API on server first, pass the CSR to 'acme.sh --sign-csr --csr ', and try again." + _err 'For more information: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#generate-csr-on-server-optional' + return 1 + elif ! _redfish_supports_private_key "${_ckey}" "${_certificate_service_endpoint}"; then + _err "Redfish server does not support this type of private key." + return 1 + fi + + _ckey_pkcs8="$("${ACME_OPENSSL_BIN:-openssl}" pkcs8 -topk8 -nocrypt -in "${_ckey}")" + + # shellcheck disable=SC2181 + if [ "$?" != '0' ] || [ -z "${_ckey_pkcs8}" ]; then + _err 'Failed to convert private key to PKCS#8 format!' + return 1 + fi + + _certificate_str="$(echo "${_ckey_pkcs8}" | cat - "${_cfullchain}" | _json_encode)" + _certificate_type='PEMchain' + else + _info "Private key and full certificate chain not available; we must be using a server-generated CSR." + _info "Uploading only leaf certificate to: ${_certificate_endpoint}" + + _certificate_str="$(_json_encode <"${_ccert}")" + _certificate_type='PEM' + fi + + _body="$(printf '{"CertificateString":"%s","CertificateType":"%s","CertificateUri":{"@odata.id":"%s"}}' "${_certificate_str}" "${_certificate_type}" "${_certificate_endpoint}")" + _redfish_rest POST "${_certificate_service_endpoint}/Actions/CertificateService.ReplaceCertificate" "${_body}" || return 1 + _code="$(_redfish_response_code)" + + if ! _startswith "${_code}" '2'; then + _err "Failed to update Redfish server TLS certificate! (HTTP ${_code})" + _err "Response: ${_response}" + return 1 + fi + + _info 'Successfully updated Redfish server TLS certificate!' + _savedeployconf DEPLOY_REDFISH_CERTIFICATE "${_certificate_endpoint}" + + if [ "${DEPLOY_REDFISH_RESTART}" = '1' ]; then + _info 'Attempting to restart BMC gracefully.' + _redfish_attempt_graceful_restart "${_manager_endpoint}" || return 0 + _info 'Successfully sent graceful restart command to BMC. After restarting, the new TLS certificate will be active.' + else + _info 'Please wait up to 20 seconds to take effect, or restart the BMC manually.' + fi + + _savedeployconf DEPLOY_REDFISH_RESTART "${DEPLOY_REDFISH_RESTART}" + + return 0 +} + +_redfish_rest() { + _method="$1" + _endpoint="$2" + _body="${3:-}" + + export _H1='OData-Version: 4.0' + + if [ "${_method}" = 'GET' ]; then + _response="$(_get "https://${DEPLOY_REDFISH_HOST}${_endpoint}")" + else + _response="$(_post "${_body}" "https://${DEPLOY_REDFISH_HOST}${_endpoint}" '' "${_method}" "${_body:+application/json}")" + fi + + _ret="$?" + _response="$(echo "${_response}" | _normalizeJson)" + + if [ "${_ret}" != '0' ]; then + _err "Error while calling ${_method} ${_endpoint}. Deploy with --insecure if current certificate is invalid. Try deploying with --debug to troubleshoot." + fi + + return "${_ret}" +} + +_redfish_response_code() { + _egrep_o <"${HTTP_HEADER}" '^HTTP[^ ]* [0-9]+' | _tail_n 1 | tr -d '\r\n' | cut -d ' ' -f 2 +} + +_redfish_get_odata_id() { + _regex='s/{' + for key in $(echo "$1" | tr '.' '\n'); do + _regex="${_regex}.*\"${key}\":{" + done + sed -n "${_regex}[^}]*\"@odata\\.id\":[ ]*\"\\([^\"]*\\)\".*/\\1/p" +} + +_redfish_get_odata_members() { + sed -n 's/.*"Members":[ ]*\[\([^]]*\)\].*/\1/p' | sed 's/,/\n/g' | sed -n 's/.*{"@odata\.id":"\([^"]*\)"}.*/\1/p' +} + +_redfish_get_odata_count() { + sed -n 's/.*"Members@odata\.count":[ ]*\([0-9][0-9]*\).*/\1/p' +} + +_redfish_log_in() { + _username="$1" + _password="$2" + + _redfish_rest GET '/redfish/v1/' || return 1 + _info "Authenticating with Redfish API: ${DEPLOY_REDFISH_HOST}" + + if [ "${DEPLOY_REDFISH_USE_BASIC_AUTH}" = '1' ]; then + _access_token="$(printf '%s:%s' "${_username}" "${_password}" | _base64)" + export _H2="Authorization: Basic ${_access_token}" + + # Verify credentials are valid + _account_service_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'AccountService')" + _redfish_rest GET "${_account_service_endpoint}" || return 1 + _code="$(_redfish_response_code)" + + if [ "${_code}" != '200' ]; then + _err "Redfish authentication failed (HTTP ${_code})" + _err "Response: ${_response}" + return 1 + fi + else + # Create new session + _sessions_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'Links.Sessions')" + _username="$(printf '%s\n' "${_username}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')" + _password="$(printf '%s\n' "${_password}" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g')" + _body="$(printf '{"UserName":"%s","Password":"%s"}' "${_username}" "${_password}")" + _redfish_rest POST "${_sessions_endpoint}" "${_body}" || return 1 + _code="$(_redfish_response_code)" + + # Verify authentication succeeded + if [ "${_code}" != '201' ]; then + _err "Redfish authentication failed (HTTP ${_code})" + _err "Response: ${_response}" + if [ "${_code}" = '401' ] && grep -qi '^WWW-Authenticate: Basic.*$' "${HTTP_HEADER}"; then + _err 'NOTE: Server supports Basic auth scheme. Consider setting DEPLOY_REDFISH_USE_BASIC_AUTH=1 and try again.' + fi + return 1 + fi + + _session="$(echo "${_response}" | _redfish_get_odata_id '')" + _auth_token="$(grep -i '^X-Auth-Token: .*$' "${HTTP_HEADER}" | _tail_n 1 | tr -d ' \r\n' | cut -d ':' -f 2)" + export _H2="X-Auth-Token: ${_auth_token}" + fi + + trap '_redfish_log_out "${_session:-}"' EXIT INT +} + +_redfish_log_out() { + _session_endpoint="${1:-}" + + _info "Logging out of Redfish session on: ${DEPLOY_REDFISH_HOST}" + + if [ -n "${_session_endpoint}" ]; then + _redfish_rest DELETE "${_session_endpoint}" || return 1 + _code="$(_redfish_response_code)" + + if ! _startswith "${_code}" '2'; then + _err "Failed to log out of Redfish server (HTTP ${_code})." + _err "Response: ${_response}" + fi + else + _info 'Using basic HTTP auth, no need to log out of Redfish API.' + fi + + export _H1= + export _H2= +} + +_redfish_get_certificate_service_endpoint() { + _redfish_rest GET '/redfish/v1/' || return 1 + _certificate_service_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'CertificateService')" + [ -n "${_certificate_service_endpoint}" ] +} + +_redfish_supports_full_certificate_chains() { + _redfish_rest GET "/redfish/v1/\$metadata" || return 1 + + _newest_certificate_schema_version="$(echo "${_response}" | + _egrep_o 'Namespace="Certificate\.v[_0-9][_0-9]*"' | + sed -n 's/.*v\([_0-9][_0-9]*\)".*/\1/p' | + sort -n -t _ -k1,1 -k2,2 -k3,3 | _tail_n 1 | tr '_' '.')" + _debug _newest_certificate_schema_version "${_newest_certificate_schema_version}" + + _schema_major=${_newest_certificate_schema_version%%.*} + _schema_patch_minor=${_newest_certificate_schema_version#*.} + _schema_minor=${_schema_patch_minor%%.*} + _schema_patch=${_schema_patch_minor#*.} + + for var in _schema_major _schema_minor _schema_patch; do + _debug "$var" "$(eval echo "\$$var")" + case "$(eval echo "\$$var")" in + '' | *[!0-9]*) return 1 ;; + *) continue ;; + esac + done + + [ "${_schema_major}" -gt '1' ] || { [ "${_schema_major}" -ge '1' ] && [ "${_schema_minor}" -ge '4' ]; } +} + +_redfish_get_action() { + unset _action _action_info + + _redfish_rest GET "$1" || return 1 + _action="$(echo "${_response}" | sed -n "s/{.*\"Actions\":{.*\"#$2\":\({[^}]*}\).*/\1/p")" + _action_info_endpoint="$(echo "${_action}" | sed -n 's/.*"@Redfish\.ActionInfo":"\([^"]*\)".*/\1/p')" + + if [ -n "${_action_info_endpoint}" ]; then + _redfish_rest GET "${_action_info_endpoint}" || return 1 + _code="$(_redfish_response_code)" + + if [ "${_code}" = '200' ]; then + _action_info="${_response}" + fi + fi +} + +_redfish_get_action_allowable_values() { + _parameter="$1" + + _allowable_values="$(echo "${_action}" | + sed -n "s/.*\"${_parameter}@Redfish\.AllowableValues\":[ ]*\[\([^]]*\)\].*/\1/p" | + tr -d '"')" + + if [ -n "${_allowable_values}" ]; then + _debug2 "Found allowable values for '${_parameter}' in '@Redfish.Action': [${_allowable_values}]" + echo "${_allowable_values}" + elif [ -n "${_action_info}" ]; then + _parameter_info="$(echo "${_action_info}" | + sed -n 's/.*"Parameters":[ ]*\[\(\({[^}]*},\{0,1\}\)*\)\].*/\1/p' | _egrep_o '\{[^\}]{1,}\}' | + grep "\"Name\":\"${_parameter}\"")" + + if [ -n "${_parameter_info}" ]; then + _debug2 "Found allowable values for '${_parameter}' in @Redfish.ActionInfo: ${_parameter_info}" + _data_type="$(echo "${_parameter_info}" | sed -n 's/.*"DataType":[ ]*"\([^"]*\)".*/\1/p')" + + case "${_data_type}" in + String) + echo "${_parameter_info}" | sed -n 's/.*"AllowableValues":[ ]*\[\([^]]*\)\].*/\1/p' | tr -d '"' + ;; + Number) + _min_value="$(echo "${_parameter_info}" | sed -n 's/.*"MinimumValue":[ ]*\([0-9]\{1,\}\).*/\1/p')" + _max_value="$(echo "${_parameter_info}" | sed -n 's/.*"MaximumValue":[ ]*\([0-9]\{1,\}\).*/\1/p')" + echo "${_min_value},${_max_value}" + ;; + esac + fi + fi +} + +_redfish_supports_private_key() { + _private_key="$1" + _certificate_service_endpoint="$2" + + if _isRSA "${_private_key}"; then + _key_algo='RSA' + elif _isEcc "${_private_key}"; then + _key_algo='ECDSA' + else + _err 'Private key uses unknown cryptographic algorithm!' + return 1 + fi + + _redfish_get_action "${_certificate_service_endpoint}" 'CertificateService.GenerateCSR' || return 1 + _allowed_key_algos="$(_redfish_get_action_allowable_values 'KeyPairAlgorithm')" + _allowed_key_bit_lengths="$(_redfish_get_action_allowable_values 'KeyBitLength')" + _allowed_key_curve_ids="$(_redfish_get_action_allowable_values 'KeyCurveId')" + + if [ -z "${_allowed_key_algos}" ]; then + _contains "${_response}" '"KeyBitLength"' && _allowed_key_algos='TPM_ALG_RSA' + _contains "${_response}" '"KeyCurveId"' && _allowed_key_algos="${_allowed_key_algos},TPM_ALG_ECDSA" + _allowed_key_algos="${_allowed_key_algos#,}" + + if [ -z "${_allowed_key_algos}" ]; then + _debug 'Failed to determine server crypto requirements, proceeding blindly.' + return 0 + fi + + _debug "GenerateCSR does not specify allowed KeyPairAlgorithm(s), so assuming ${_allowed_key_algos}." + fi + + _debug _key_algo "${_key_algo}" + _debug _allowed_key_algos "${_allowed_key_algos}" + + case "${_key_algo}:${_allowed_key_algos}" in + RSA:*RSA*) + _min_key_length="$(echo "${_allowed_key_bit_lengths}" | cut -d ',' -f 1)" + _max_key_length="${_allowed_key_bit_lengths##*,}" + _debug _min_key_length "${_min_key_length}" + _debug _max_key_length "${_max_key_length}" + + if [ -z "${_min_key_length}" ] && [ -z "${_max_key_length}" ]; then + _debug "Server supports RSA private keys but doesn't specify minimum/maximum bit length, proceeding blindly." + return 0 + fi + + # shellcheck disable=SC2154 # Le_Keylength is set by acme.sh core, not this hook + if [ "${Le_Keylength}" -lt "${_min_key_length:-${Le_Keylength}}" ] || [ "${Le_Keylength}" -gt "${_max_key_length:-${Le_Keylength}}" ]; then + _err "Unsupported RSA private key length ${Le_Keylength}!" + _err "Please re-run acme.sh with --keylength set to a value between ${_min_key_length} and ${_max_key_length}." + return 1 + fi + ;; + ECDSA:*ECDSA*) + _debug _allowed_key_curve_ids "${_allowed_key_curve_ids}" + + if [ -z "${_allowed_key_curve_ids}" ]; then + _debug "Server supports ECDSA private keys but doesn't specify which curves, proceeding blindly." + return 0 + fi + + # shellcheck disable=SC2154 # Le_Keylength is set by acme.sh core, not this hook + if ! _contains "${_allowed_key_curve_ids}" "TPM_ECC_NIST_P${Le_Keylength#ec-}"; then + _err "Unsupported ECDSA private key type! Server supports only: ${_allowed_key_curve_ids}" + return 1 + fi + ;; + *) + _err "Unsupported key pair algorithm ${_key_algo}! Server supports only: ${_allowed_key_algos}" + return 1 + ;; + esac +} + +_redfish_get_primary_manager_endpoint() { + _redfish_rest GET '/redfish/v1/' || return 1 + _managers_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'Managers')" + _redfish_rest GET "${_managers_endpoint}" || return 1 + _num_managers="$(echo "${_response}" | _redfish_get_odata_count)" + + if [ "${_num_managers}" != '1' ]; then + _all_managers="$(echo "${_response}" | _redfish_get_odata_members | tr '\n' ',')" + _err "Multiple Redfish managers identified (${_all_managers}), but expected exactly one." + _err 'Please specify the correct manager in DEPLOY_REDFISH_MANAGER.' + return 1 + fi + + _manager_endpoint="$(echo "${_response}" | _redfish_get_odata_members | _head_n 1)" + [ -n "${_manager_endpoint}" ] +} + +_redfish_get_manager_certificate_endpoint() { + _manager_endpoint="$1" + + _redfish_rest GET "${_manager_endpoint}" || return 1 + _protocol_endpoint="$(echo "${_response}" | _redfish_get_odata_id 'NetworkProtocol')" + _redfish_rest GET "${_protocol_endpoint}/HTTPS/Certificates" || return 1 + _num_certificates="$(echo "${_response}" | _redfish_get_odata_count)" + + if [ -z "${_num_certificates}" ]; then + _certificate_endpoint="${_protocol_endpoint}/HTTPS/Certificates/1" + return 0 + elif [ "${_num_certificates}" != '1' ]; then + _all_certificates="$(echo "${_response}" | _redfish_get_odata_members | tr '\n' ',')" + _err "Multiple web service HTTPS certificates identified (${_all_certificates}), but expected exactly one." + _err "Please specify which certificate should be replaced in DEPLOY_REDFISH_CERTIFICATE." + return 1 + fi + + _certificate_endpoint="$(echo "${_response}" | _redfish_get_odata_members | _head_n 1)" + [ -n "${_certificate_endpoint}" ] +} + +_redfish_attempt_graceful_restart() { + _manager_endpoint="$1" + + _redfish_get_action "${_manager_endpoint}" 'Manager.Reset' || return 1 + _allowed_reset_types="$(_redfish_get_action_allowable_values 'ResetType')" + + if ! _contains "${_allowed_reset_types}" 'GracefulRestart'; then + _err "BMC doesn't support graceful restarts. Please wait up to 20 seconds to take effect, or restart the BMC manually." + return 1 + fi + + _redfish_rest POST "${_manager_endpoint}/Actions/Manager.Reset" '{"ResetType":"GracefulRestart"}' || return 1 + _code="$(_redfish_response_code)" + + if ! _startswith "${_code}" '2'; then + _err "Failed to gracefully restart BMC! (HTTP ${_code})" + _err "Response: ${_response}" + return 1 + fi +}