summaryrefslogtreecommitdiffstats
path: root/tools/cibsecret.in
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/cibsecret.in440
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: