2
0
mirror of https://github.com/xcat2/xcat-core.git synced 2026-09-11 12:06:24 +00:00
Files
xcat-core/xCAT/postscripts/sudoer
T
Vinícius Ferrão 005a32a721 fix(sudoer): take the password from the passwd table
The sudoer postscript created the xcat account with the fixed password
rootpw. The rebased change from #6166 moved the password to the -p
option, which shows it in ps on the node and spreads it over
postscripts attributes.

The postscript now asks the management node for the password field of
the passwd row key=system,username=<sudoer> over getcredentials, the
way remoteshell gets the root hash under secureroot, and applies it:
the hash, or "!" when the table has no password for the account. sudo
and the SSH key are granted only after that. A missing reply, a refused
request, or a failed usermod fails the postscript and leaves the
account unprivileged. -u still selects the user name and defaults to
xcat. -p is rejected.

An existing login account is kept, with the other keys in its
authorized_keys. Root, accounts outside UID_MIN..UID_MAX, and accounts
without a login shell are refused. The sudo rule lives in
/etc/sudoers.d/xcat-sudoer, which records its account: a rerun with
another name revokes the rule, the password, and the cluster key of the
previous account, and the lines the old postscript appended to
/etc/sudoers are moved out after visudo accepts the result. Every
privilege write is checked, and a failed run removes the managed rule.
Without sudoers.d the rule is appended once to /etc/sudoers.
2026-09-03 20:39:09 -03:00

232 lines
7.5 KiB
Bash
Executable File

#!/bin/bash
# IBM(c) 2013 EPL license http://www.eclipse.org/legal/epl-v10.html
#------------------------------------------------------------------------------
#
# Setup a sudoer named xcat and copy the xCAT public SSH key in its
# authorized_keys file. Only applies to Linux.
#
# The sudoer gets the password stored in the passwd table under
# key=system,username=<sudoer>. Without that row the account has no
# password and accepts only the SSH key.
#
#------------------------------------------------------------------------------
function usage() {
echo ""
echo "Usage: $0 [-u username]"
echo -e "\t-u sudoer user name, xcat by default"
exit 1
}
SUDOER="xcat"
while getopts "u:" opt;
do
case $opt in
u) SUDOER="$OPTARG";;
*) usage;;
esac
done
if [[ ! "$SUDOER" =~ ^[A-Za-z_][A-Za-z0-9_.-]{0,31}$ ]]
then
usage;
fi
if [ -n "$LOGLABEL" ]; then
log_label=$LOGLABEL
else
log_label="xcat"
fi
if [ "$(uname -s|tr 'A-Z' 'a-z')" = "linux" ];then
str_dir_name=`dirname $0`
. $str_dir_name/xcatlib.sh
fi
master=$MASTER
useflowcontrol=0
if [ "$USEFLOWCONTROL" = "YES" ] || [ "$USEFLOWCONTROL" = "yes" ] || [ "$USEFLOWCONTROL" = "1" ]; then
useflowcontrol=1
fi
MANAGED=/etc/sudoers.d/xcat-sudoer
HOSTKEYS="/xcatpost/hostkeys/ssh_host_rsa_key.pub /xcatpost/hostkeys/ssh_host_dsa_key.pub"
LEGACY_RULE="xcat ALL=(ALL) NOPASSWD: ALL"
LEGACY_TTY="Defaults:xcat !requiretty"
function log() {
logger -t $log_label -p "local4.$1" "sudoer: $2"
}
# A failed run keeps no sudo rule from an earlier run
function fail() {
log err "$1"
rm -f "$MANAGED"
exit 1
}
# ssh-keygen leaves a trailing space after an empty comment and the previous
# version of this postscript wrote the key without it
function cluster_key() {
sed -e 's/[[:space:]]*$//' "$1"
}
function valid_sudoers() {
command -v visudo >/dev/null 2>&1 || return 0
visudo -cf "$1" >/dev/null 2>&1
}
# Take the lines the previous version of this postscript appended out of
# /etc/sudoers. The rule now lives in the managed file.
function migrate_legacy_sudoers() {
grep -qxF "$LEGACY_RULE" /etc/sudoers || return 0
local tmp
tmp=$(mktemp /etc/sudoers.xcat.XXXXXX) || return 1
grep -vxF -e "$LEGACY_RULE" -e "$LEGACY_TTY" /etc/sudoers > "$tmp"
if ! valid_sudoers "$tmp" || ! chmod 0440 "$tmp" || ! mv -f "$tmp" /etc/sudoers; then
rm -f "$tmp"
return 1
fi
legacy_xcat=1
}
# The account of an earlier run keeps its login, without sudo, password,
# or the cluster key
function revoke_account() {
local name=$1 home keyfile pubkey tmp
[ "$name" = "$SUDOER" ] && return 0
getent passwd "$name" >/dev/null || return 0
/usr/sbin/usermod -p '!' "$name" || return 1
home=$(getent passwd "$name" | cut -f6 -d :)
keyfile="$home/.ssh/authorized_keys"
if [[ "$home" == /* ]] && [ -f "$keyfile" ]; then
for pubkey in $HOSTKEYS; do
[ -r "$pubkey" ] || continue
tmp=$(mktemp "$keyfile.XXXXXX") || return 1
grep -vxF "$(cluster_key "$pubkey")" "$keyfile" > "$tmp"
cat "$tmp" > "$keyfile" || { rm -f "$tmp"; return 1; }
rm -f "$tmp"
done
fi
log info "revoked the previous sudoer $name"
}
function grant_sudo() {
local tmp
tmp=$(mktemp /etc/sudoers.d/xcat-sudoer.XXXXXX) || return 1
{
echo "# xCAT sudoer: $SUDOER"
echo "$SUDOER ALL=(ALL) NOPASSWD: ALL"
if [ -e "/etc/redhat-release" ]; then
echo "Defaults:$SUDOER !requiretty"
fi
} > "$tmp" || { rm -f "$tmp"; return 1; }
if ! valid_sudoers "$tmp" || ! chmod 0440 "$tmp" || ! mv -f "$tmp" "$MANAGED"; then
rm -f "$tmp"
return 1
fi
}
function append_sudo() {
local rule="$SUDOER ALL=(ALL) NOPASSWD: ALL"
grep -qxF "$rule" /etc/sudoers || echo "$rule" >> /etc/sudoers || return 1
if [ -e "/etc/redhat-release" ]; then
grep -qxF "Defaults:$SUDOER !requiretty" /etc/sudoers || echo "Defaults:$SUDOER !requiretty" >> /etc/sudoers || return 1
fi
}
# Add the cluster host keys and keep the keys that are already there
function grant_keys() {
local keyfile="$sudoer_home/.ssh/authorized_keys" pubkey key
mkdir -p "$sudoer_home/.ssh" && touch "$keyfile" || return 1
for pubkey in $HOSTKEYS; do
[ -r "$pubkey" ] || continue
key=$(cluster_key "$pubkey")
grep -qxF "$key" "$keyfile" || echo "$key" >> "$keyfile" || return 1
done
chmod 0644 "$keyfile" && chown "$SUDOER" "$keyfile"
}
# Never manage root, a service account, or an account without a login shell
uid_min=$(awk '$1 == "UID_MIN" { print $2 }' /etc/login.defs 2>/dev/null)
uid_max=$(awk '$1 == "UID_MAX" { print $2 }' /etc/login.defs 2>/dev/null)
account=$(getent passwd "$SUDOER")
if [ -n "$account" ]; then
uid=$(echo "$account" | cut -f3 -d :)
shell=$(echo "$account" | cut -f7 -d :)
if [ "$uid" -lt "${uid_min:-1000}" ] || [ "$uid" -gt "${uid_max:-60000}" ]; then
fail "$SUDOER is a system account (uid $uid), leaving it alone"
fi
case "$shell" in
*/nologin|*/false|"") fail "$SUDOER has no login shell, leaving it alone";;
esac
fi
# Create sudoer
if [ -z "$account" ]; then
/usr/sbin/useradd -m "$SUDOER" || fail "unable to create $SUDOER"
fi
# The password field comes from the passwd table on the management node,
# the same way remoteshell gets the root hash when secureroot is enabled.
# The reply is the hash, or "!" when the table has no password for the
# account. sudo and the SSH key are granted only once the field is applied.
allowcred.awk &
CREDPID=$!
sleep 1
response=""
for attempt in 1 2 3; do
if [ $useflowcontrol = "1" ]; then
log info "sending xcatflowrequest $master 3001"
/xcatpost/xcatflowrequest $master 3001
fi
response=$(getcredentials.awk xcat_secure_pw:$SUDOER | grep -E -v '</{0,1}xcatresponse>|</{0,1}serverdone>' | sed -e 's/&lt;/</' -e 's/&gt;/>/' -e 's/&amp;/&/' -e 's/&quot/"/' -e "s/&apos;/'/")
[ -n "$response" ] && break
[ $attempt -lt 3 ] && sleep $((attempt * 5))
done
{ kill -9 $CREDPID && wait $CREDPID; } 2>/dev/null
SUDOERPWFIELD=$(echo "$response" | sed -n 's%.*<content>\(.*\)</content>.*%\1%p')
if [ -z "$SUDOERPWFIELD" ]; then
ERR_MSG=$(echo "$response" | sed -n 's%.*<error>\(.*\)</error>.*%\1%p')
fail "no password for $SUDOER, leaving the account unprivileged: ${ERR_MSG:-no reply from $master}"
fi
/usr/sbin/usermod -p "$SUDOERPWFIELD" "$SUDOER" || fail "unable to set the password of $SUDOER, leaving the account unprivileged"
if [ "$SUDOERPWFIELD" = "!" ]; then
log info "$SUDOER has no password in the passwd table, the account is locked"
else
log info "set the password of $SUDOER from the passwd table"
fi
# Find sudoer home
sudoer_home=$(getent passwd "$SUDOER" | cut -f6 -d :)
if [[ "$sudoer_home" != /* ]]; then
fail "no home directory for $SUDOER, leaving the account unprivileged"
fi
# Configuration for the sudoer
if [ -d /etc/sudoers.d ] && grep -qE '^[#@]includedir[[:space:]]+/etc/sudoers.d' /etc/sudoers; then
previous=$(sed -n '1s/^# xCAT sudoer: //p' "$MANAGED" 2>/dev/null)
legacy_xcat=""
migrate_legacy_sudoers || fail "unable to take the legacy rule out of /etc/sudoers"
if [ -n "$legacy_xcat" ]; then
revoke_account xcat || fail "unable to revoke the legacy sudoer xcat"
fi
if [ -n "$previous" ]; then
revoke_account "$previous" || fail "unable to revoke the previous sudoer $previous"
fi
grant_sudo || fail "unable to write $MANAGED"
else
append_sudo || fail "unable to add the rule of $SUDOER to /etc/sudoers"
fi
grant_keys || fail "unable to install the cluster key for $SUDOER"
# Restart the SSHD for syncfiles postscript to do the sync work
logger -t $log_label -p local4.info "Restarting SSHD"
#if [ -f "/etc/debian_version" ];then
# service ssh restart
#else
# service sshd restart
#fi
restartservice ssh