diff options
Diffstat (limited to '')
-rw-r--r-- | tools/cibsecret.in | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/tools/cibsecret.in b/tools/cibsecret.in new file mode 100644 index 0000000..4569863 --- /dev/null +++ b/tools/cibsecret.in @@ -0,0 +1,440 @@ +#!@BASH_PATH@ + +# Copyright 2011-2020 the Pacemaker project contributors +# +# The version control history for this file may have further details. +# +# This source code is licensed under the GNU General Public License version 2 +# or later (GPLv2+) WITHOUT ANY WARRANTY. +# + +# cibsecret +# +# Manage the secrets directory (by default, /var/lib/pacemaker/lrm/secrets). +# Secrets are ASCII files, holding one value per file: +# <secrets-directory>/<rsc>/<param> + +# These constants must track crm_exit_t values +CRM_EX_OK=0 +CRM_EX_ERROR=1 +CRM_EX_NOT_INSTALLED=5 +CRM_EX_USAGE=64 +CRM_EX_UNAVAILABLE=69 +CRM_EX_OSFILE=72 +CRM_EX_CONFIG=78 +CRM_EX_DIGEST=104 +CRM_EX_NOSUCH=105 +CRM_EX_EXISTS=108 + +LRM_CIBSECRETS="@LRM_CIBSECRETS_DIR@" + +PROG="$(basename "$0")" +SSH_OPTS="-o StrictHostKeyChecking=no" +MAGIC="lrm://" + +usage() { + cat <<EOF +cibsecret - manage sensitive information in Pacemaker CIB + +Usage: + $PROG [<options>] <command> [<parameters>] + +Options: + --help Show this message, then exit + --version Display version information, then exit + -C Don't read or write the CIB + +Commands and their parameters: + set <resource-id> <resource-parameter> <value> + Set the value of a sensitive resource parameter. + + get <resource-id> <resource-parameter> + Display the locally stored value of a sensitive resource parameter. + + check <resource-id> <resource-parameter> + Verify that the locally stored value of a sensitive resource parameter + matches its locally stored MD5 hash. + + stash <resource-id> <resource-parameter> + Make a non-sensitive resource parameter that is already in the CIB + sensitive (move its value to a locally stored and protected file). + This may not be used with -C. + + unstash <resource-id> <resource-parameter> + Make a sensitive resource parameter that is already in the CIB + non-sensitive (move its value from the locally stored file to the CIB). + This may not be used with -C. + + delete <resource-id> <resource-parameter> + Remove a sensitive resource parameter value. + + sync + Copy all locally stored secrets to all other nodes. + +This command manages sensitive resource parameter values that should not be +stored directly in Pacemaker's Cluster Information Base (CIB). Such values +are handled by storing a special string directly in the CIB that tells +Pacemaker to look in a separate, protected file for the actual value. + +The secret files are not encrypted, but protected by file system permissions +such that only root can read or modify them. + +Since the secret files are stored locally, they must be synchronized across all +cluster nodes. This command handles the synchronization using (in order of +preference) pssh, pdsh, or ssh, so one of those must be installed. Before +synchronizing, this command will ping the cluster nodes to determine which are +alive, using fping if it is installed, otherwise the ping command. Installing +fping is strongly recommended for better performance. + +Known limitations: + + This command can only be run from full cluster nodes (not Pacemaker Remote + nodes). + + Changes are not atomic, so the cluster may use different values while a + change is in progress. To avoid problems, it is recommended to put the + cluster in maintenance mode when making changes with this command. + + Changes in secret values do not trigger an agent reload or restart of the + affected resource, since they do not change the CIB. If a response is + desired before the next cluster recheck interval, any CIB change (such as + setting a node attribute) will trigger it. + + If any node is down when changes to secrets are made, or a new node is + later added to the cluster, it may have different values when it joins the + cluster, before "$PROG sync" is run. To avoid this, it is recommended to + run the sync command (from another node) before starting Pacemaker on the + node. + +Examples: + + $PROG set ipmi_node1 passwd SecreT_PASS + + $PROG get ipmi_node1 passwd + + $PROG check ipmi_node1 passwd + + $PROG stash ipmi_node2 passwd + + $PROG sync +EOF + exit "$1" +} + +check_usage() { + case "$1" in + set) [ "$2" -ne 4 ] && [ "$2" -ne 3 ] && usage 1 ;; + get) [ "$2" -ne 3 ] && usage 1 ;; + check) [ "$2" -ne 3 ] && usage 1 ;; + stash) [ "$2" -ne 3 ] && usage 1 ;; + unstash) [ "$2" -ne 3 ] && usage 1 ;; + delete) [ "$2" -ne 3 ] && usage 1 ;; + sync) [ "$2" -ne 1 ] && usage 1 ;; + --help) usage $CRM_EX_OK ;; + --version) crm_attribute --version; exit $? ;; + *) usage $CRM_EX_USAGE ;; + esac +} + +fatal() { + rc=$1 + shift + echo "ERROR: $*" + exit $rc +} + +warn() { + echo "WARNING: $*" +} + +info() { + echo "INFO: $*" +} + +check_env() { + which md5sum >/dev/null 2>&1 || + fatal $CRM_EX_NOT_INSTALLED "please install md5sum to run $PROG" + if which pssh >/dev/null 2>&1; then + rsh=pssh_fun + rcp_to_from=pscp_fun + + # -q is a SUSE patch not present in upstream pssh + PSSH_QUIET_OPTION="" + pssh -q 2>&1|grep "no such option: -q" > /dev/null || + PSSH_QUIET_OPTION="-q" + elif which pdsh >/dev/null 2>&1; then + rsh=pdsh_fun + rcp_to_from=pdcp_fun + elif which ssh >/dev/null 2>&1; then + rsh=ssh_fun + rcp_to_from=scp_fun + else + fatal $CRM_EX_NOT_INSTALLED "please install pssh, pdsh, or ssh to run $PROG" + fi + ps -ef | grep '[p]acemaker-controld' >/dev/null || + fatal $CRM_EX_UNAVAILABLE "pacemaker not running? $PROG needs pacemaker" +} + +# This must be called (and return success) before calling $rsh or $rcp_to_from +get_live_peers() { + # Get local node name + GLP_LOCAL_NODE="$(crm_node -n)" + [ $? -eq 0 ] || fatal $CRM_EX_UNAVAILABLE "couldn't get local node name" + + # Get a list of all other cluster nodes + GLP_ALL_PEERS="$(crmadmin -N -q)" + [ $? -eq 0 ] || fatal $CRM_EX_UNAVAILABLE "couldn't determine cluster nodes" + GLP_ALL_PEERS="$(echo "$GLP_ALL_PEERS" | grep -v "^${GLP_LOCAL_NODE}$")" + + # Make a list of those that respond to pings + if [ "$(id -u)" = "0" ] && which fping >/dev/null 2>&1; then + LIVE_NODES=$(fping -a $GLP_ALL_PEERS 2>/dev/null) + else + LIVE_NODES="" + for GLP_NODE in $GLP_ALL_PEERS; do \ + ping -c 2 -q "$GLP_NODE" >/dev/null 2>&1 && + LIVE_NODES="$LIVE_NODES $GLP_NODE" + done + fi + + # Warn the user about any that didn't respond to pings + GLP_DOWN="$( (for GLP_NODE in $LIVE_NODES $GLP_ALL_PEERS; do echo "$GLP_NODE"; done) | sort | uniq -u)" + if [ "$(echo "$GLP_DOWN" | wc -w)" = "1" ]; then + warn "node $GLP_DOWN is down" + warn "you'll need to update it using \"$PROG sync\" later" + elif [ -n "$GLP_DOWN" ]; then + warn "nodes $(echo "$GLP_DOWN" | tr '\n' ' ')are down" + warn "you'll need to update them using \"$PROG sync\" later" + fi + + if [ "$LIVE_NODES" = "" ]; then + info "no other nodes live" + return 1 + fi + return 0 +} + +pssh_fun() { + pssh $PSSH_QUIET_OPTION -i -H "$LIVE_NODES" -x "$SSH_OPTS" -- "$@" +} + +pscp_fun() { + PSCP_DEST="$1" + shift + pscp $PSSH_QUIET_OPTION -H "$LIVE_NODES" -x "-pr" -x "$SSH_OPTS" -- "$@" "$PSCP_DEST" +} + +pdsh_fun() { + PDSH_NODES=$(echo "$LIVE_NODES" | tr '[:space:]' ',') + export PDSH_SSH_ARGS_APPEND="$SSH_OPTS" + pdsh -w "$PDSH_NODES" -- "$@" +} + +pdcp_fun() { + PDCP_DEST="$1" + shift + PDCP_NODES=$(echo "$LIVE_NODES" | tr '[:space:]' ',') + export PDSH_SSH_ARGS_APPEND="$SSH_OPTS" + pdcp -pr -w "$PDCP_NODES" -- "$@" "$PDCP_DEST" +} + +ssh_fun() { + for SSH_NODE in $LIVE_NODES; do + ssh $SSH_OPTS "$SSH_NODE" -- "$@" || return + done +} + +scp_fun() { + SCP_DEST="$1" + shift + for SCP_NODE in $LIVE_NODES; do + scp -pqr $SSH_OPTS "$@" "$SCP_NODE:$SCP_DEST" || return + done +} + +# TODO: this procedure should be replaced with csync2 +# provided that csync2 has already been configured +sync_files() { + get_live_peers || return + if [ "$cmd" != "delete" ]; then + info "syncing $LRM_CIBSECRETS to $(echo "$LIVE_NODES" | tr '\n' ' ') ..." + else + info "deleting $LRM_CIBSECRETS from $(echo "$LIVE_NODES" | tr '\n' ' ') ..." + fi + $rsh rm -rf "$LRM_CIBSECRETS" && + $rsh mkdir -p "$(dirname "$LRM_CIBSECRETS")" && + $rcp_to_from "$(dirname "$LRM_CIBSECRETS")" "$LRM_CIBSECRETS" +} + +sync_one() { + SO_FILE="$1" + get_live_peers || return + if [ "$cmd" != "delete" ]; then + info "syncing $SO_FILE to $(echo "$LIVE_NODES" | tr '\n' ' ') ..." + else + info "deleting $SO_FILE from $(echo "$LIVE_NODES" | tr '\n' ' ') ..." + fi + $rsh mkdir -p "$(dirname "$SO_FILE")" && + if [ -f "$SO_FILE" ]; then + $rcp_to_from "$(dirname "$SO_FILE")" "$SO_FILE" "${SO_FILE}.sign" + else + $rsh rm -f "$SO_FILE" "${SO_FILE}.sign" + fi +} + +is_secret() { + # assume that the secret is in the CIB if we cannot talk to cib + [ "$NO_CRM" ] || test "$1" = "$MAGIC" +} + +check_cib_rsc() { + CCR_OUT="$($NO_CRM crm_resource -r "$1" -W 2>&1)" || fatal $CRM_EX_NOSUCH "$CCR_OUT" +} + +get_cib_param() { + GCP_RSC="$1" + GCP_PARAM="$2" + $NO_CRM crm_resource -r "$GCP_RSC" -g "$GCP_PARAM" 2>/dev/null +} + +set_cib_param() { + SET_RSC="$1" + SET_PARAM="$2" + SET_VAL="$3" + $NO_CRM crm_resource -r "$SET_RSC" -p "$SET_PARAM" -v "$SET_VAL" 2>/dev/null +} + +remove_cib_param() { + RM_RSC="$1" + RM_PARAM="$2" + $NO_CRM crm_resource -r "$RM_RSC" -d "$RM_PARAM" 2>/dev/null +} + +localfiles() { + LF_CMD="$1" + LF_RSC="$2" + LF_PARAM="$3" + LF_VALUE="$4" + LF_FILE="$LRM_CIBSECRETS/$LF_RSC/$LF_PARAM" + case "$LF_CMD" in + get) + cat "$LF_FILE" 2>/dev/null + true + ;; + + getsum) + cat "${LF_FILE}.sign" 2>/dev/null + true + ;; + + set) + LF_SUM="$(printf %s "$LF_VALUE" | md5sum)" || + fatal $CRM_EX_ERROR "md5sum failed to produce hash for resource $LF_RSC parameter $LF_PARAM" + LF_SUM="$(echo "$LF_SUM" | awk '{print $1}')" + mkdir -p "$(dirname "$LF_FILE")" && + echo "$LF_VALUE" > "$LF_FILE" && + echo "$LF_SUM" > "${LF_FILE}.sign" && + sync_one "$LF_FILE" + ;; + + remove) + rm -f "$LF_FILE" "${LF_FILE}.sign" + sync_one "$LF_FILE" + ;; + esac +} + +cibsecret_set() { + CS_VALUE="$1" + + if [ "$2" -ne 4 ]; then + read -p "Enter value: " CS_VALUE + fi + + check_cib_rsc "$rsc" + CIBSET_CURRENT="$(get_cib_param "$rsc" "$param")" + [ -z "$NO_CRM" ] && + [ ! -z "$CIBSET_CURRENT" ] && + [ "$CIBSET_CURRENT" != "$MAGIC" ] && + [ "$CIBSET_CURRENT" != "$CS_VALUE" ] && + fatal $CRM_EX_CONFIG "CIB value <$CIBSET_CURRENT> different for $rsc parameter $param; please delete it first" + localfiles set "$rsc" "$param" "$CS_VALUE" && + set_cib_param "$rsc" "$param" "$MAGIC" +} + +cibsecret_check() { + check_cib_rsc "$rsc" + is_secret "$(get_cib_param "$rsc" "$param")" || + fatal $CRM_EX_CONFIG "resource $rsc parameter $param not set as secret, nothing to check" + CSC_LOCAL_SUM="$(localfiles getsum "$rsc" "$param")" + [ "$CSC_LOCAL_SUM" ] || + fatal $CRM_EX_OSFILE "no MD5 hash for resource $rsc parameter $param" + CSC_LOCAL_VALUE="$(localfiles get "$rsc" "$param")" + CSC_CALC_SUM="$(printf "%s" "$CSC_LOCAL_VALUE" | md5sum | awk '{print $1}')" + [ "$CSC_CALC_SUM" = "$CSC_LOCAL_SUM" ] || + fatal $CRM_EX_DIGEST "MD5 hash mismatch for resource $rsc parameter $param" +} + +cibsecret_get() { + cibsecret_check + localfiles get "$rsc" "$param" +} + +cibsecret_delete() { + check_cib_rsc "$rsc" + localfiles remove "$rsc" "$param" && remove_cib_param "$rsc" "$param" +} + +cibsecret_stash() { + [ "$NO_CRM" ] && fatal $CRM_EX_USAGE "no access to Pacemaker, stash not supported" + check_cib_rsc "$rsc" + CIBSTASH_CURRENT="$(get_cib_param "$rsc" "$param")" + [ "$CIBSTASH_CURRENT" = "" ] && + fatal $CRM_EX_NOSUCH "nothing to stash for resource $rsc parameter $param" + is_secret "$CIBSTASH_CURRENT" && + fatal $CRM_EX_EXISTS "resource $rsc parameter $param already set as secret, nothing to stash" + cibsecret_set "$CIBSTASH_CURRENT" 4 +} + +cibsecret_unstash() { + [ "$NO_CRM" ] && fatal $CRM_EX_USAGE "no access to Pacemaker, unstash not supported" + UNSTASH_LOCAL_VALUE="$(localfiles get "$rsc" "$param")" + [ "$UNSTASH_LOCAL_VALUE" = "" ] && + fatal $CRM_EX_NOSUCH "nothing to unstash for resource $rsc parameter $param" + check_cib_rsc "$rsc" + is_secret "$(get_cib_param "$rsc" "$param")" || + warn "resource $rsc parameter $param not set as secret, but we have local value so proceeding anyway" + localfiles remove "$rsc" "$param" && + set_cib_param "$rsc" "$param" "$UNSTASH_LOCAL_VALUE" +} + +cibsecret_sync() { + sync_files +} + +# Grab arguments +if [ "$1" = "-C" ]; then + NO_CRM=':' + shift +fi +cmd="$1" +rsc="$2" +param="$3" +value="$4" + +# Ensure we have everything we need +check_usage "$cmd" $# +check_env +umask 0077 + +# for dirname() function (@TODO why are we replacing dirname?) +. "@OCF_ROOT_DIR@/lib/heartbeat/ocf-shellfuncs" + +"cibsecret_$cmd" "$value" $# +rc=$? + +if [ $rc -ne 0 ]; then + fatal $CRM_EX_ERROR "$cmd(): failed with rc: $rc" +fi + +# vim: set expandtab tabstop=8 softtabstop=4 shiftwidth=4 textwidth=80: |