#!/bin/bash
#==============================================================================
# Copyright and license info is available in the LICENSE file included with
# the Server Deployment Package (SDP), and also available online:
# https://swarm.workshop.perforce.com/projects/perforce-software-sdp/view/main/LICENSE
#------------------------------------------------------------------------------
#==============================================================================
# Declarations and Environment
set -u
declare ThisScript=${0##*/}
declare Version=1.0.5
declare ThisUser=
declare LiveSudoersFile=
declare TempSudoersFile=
declare BackupSudoersFile=
declare SDPInstance=
declare P4Service=
declare SystemCtlCmd=
declare TmpFile=
declare -i LimitedSudoers=2
declare -a SystemCtlCmdList
declare Service=
declare -i ErrorCount=0
declare -i i=0
declare -i NoOp=1
declare -i Force=0
declare Log=
declare H1="=============================================================================="
declare H2="------------------------------------------------------------------------------"
declare RootHome=
#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }
function errmsg () { msg "\\nError: ${1:-Unknown Error}\\n"; ErrorCount+=1; }
function bail () { errmsg "${1:-Unknown Error}"; exit "$ErrorCount"; }
#------------------------------------------------------------------------------
# Function: terminate
function terminate
{
# Disable signal trapping.
trap - EXIT SIGINT SIGTERM
[[ "$Log" != off ]] && \
msg "Log is: $Log\\n${H1}\\n"
# With the trap removed, exit.
exit "$ErrorCount"
}
#------------------------------------------------------------------------------
# Function: usage (required function)
#
# Input:
# $1 - style, either -h (for short form) or -man (for man-page like format).
# The default is -h.
#
# $2 - error message (optional). Specify this if usage() is called due to
# user error, in which case the given message displayed first, followed by the
# standard usage message (short or long depending on $1). If displaying an
# error, usually $1 should be -h so that the longer usage message doesn't
# obscure the error message.
#
# Sample Usage:
# usage
# usage -h
# usage -man
# usage -h "Incorrect command line usage."
#------------------------------------------------------------------------------
function usage
{
declare style=${1:--h}
declare errorMessage=${2:-Unset}
if [[ "$errorMessage" != Unset ]]; then
msg "\\n\\nUsage Error:\\n\\n$errorMessage\\n\\n"
fi
msg "USAGE for $ThisScript v$Version:
$ThisScript {-full|-limited} [-y [-f]] [-L <log>] [-D]
or
$ThisScript [-h|-man]
"
if [[ $style == -man ]]; then
msg "
DESCRIPTION:
This script generates a sudoers file for the OS user that
owns /p4/common, which is expected to be the same user that the
Perforce Helix Core service runs as (typically 'perforce').
By default, the sudoers file is generated for review. If the '-y'
option is specified, the newly generated files is installed as
the live sudoers file by copying to /etc/sudoers.d/<OSUSER> and
adjusting permissions to 0400.
If '-full' (full sudo) is specified, a one-line sudoers file is
generated that looks something like this:
perforce ALL=(ALL) NOPASSWD: ALL
If '-limited' is specified, a limited sudoers file is generated
granting only necessary access to the perforce user.
If the sudoers file already exits, it will not be updated unless
'-f' (force) is proivided.
The limited sudoers is recommended for production deployments.
OPTIONS:
-full
Specify '-full' to indicate that a sudoers file is to be generated
granting full root access to the server machine.
The '-full' or '-limited' option must be specified.
This option is discouraged as it is not as secure as the
'-limited' option.
-limited
Specify '-limited' to indicate that a sudoers file is to be
generated granting limited access to the server machine.
The '-full' or '-limited' option must be specified.
This option is recommended for optimal security.
-y This is confirmation to install the generated sudoers as the live
sudoers file.
-f Specify '-f' to overwite an existing limited sudoers file,
/etc/sudoers.d/<OSUSER>
-L <log>
Specify the path to a log file, or the special value 'off' to disable
logging. By default, all output (stdout and stderr) goes to:
$RootHome/${ThisScript%.sh}.<Datestamp>.log
NOTE: This script is self-logging. That is, output displayed on the screen
is simultaneously captured in the log file.
-D Enable bash 'set -x' extreme debugging verbosity.
HELP OPTIONS:
-h Display short help message
-man Display man-style help message
EXAMPLES:
EXAMPLE 1: Generate a limited sudoers file for review.
cd /p4/sdp/Server/Unix/setup
./gen_sudoers.sh -limited
EXAMPLE 2: Generate a limited sudoers file and install it.
cd /p4/sdp/Server/Unix/setup
./gen_sudoers.sh -limited -y
EXAMPLE 3: Generate a limited sudoers file and install it, replacing an
existing one.
cd /p4/sdp/Server/Unix/setup
./gen_sudoers.sh -limited -f -y
EXAMPLE 4: Generate a full sudoers file and install it, replacing an
cd /p4/sdp/Server/Unix/setup
./gen_sudoers.sh -full -f -y
"
fi
exit 2
}
#==============================================================================
# Command Line Processing
#------------------------------------------------------------------------------
# Get the home for Root. This is reliably /root on Linux, but varies in UNIX
# distros. If /root does not exist, attempt detection with getent. As
# a fallback, use /tmp. This is only used for a location of the log file.
# It must be set before processing command line arguments becuase this value
# is used in the usage() function.
if [[ -d /root ]]; then
RootHome=/root
else
RootHome=$(getent passwd root | cut -d: -f6)
fi
RootHome=${RootHome:-/tmp}
#------------------------------------------------------------------------------
declare -i shiftArgs=0
set +u
while [[ $# -gt 0 ]]; do
case $1 in
(-h) usage -h;;
(-man) usage -man;;
(-L) Log="$2"; shiftArgs=1;;
(-f) Force=1;;
(-full) LimitedSudoers=0;;
(-limited) LimitedSudoers=1;;
(-y) NoOp=0;;
(-D) set -x;; # Debug; use 'set -x' mode.
(-*) usage -h "Unknown flag ($1).";;
(*) usage -h "Unknown parameter ($1).";;
esac
# Shift (modify $#) the appropriate number of times.
shift; while [[ $shiftArgs -gt 0 ]]; do
[[ $# -eq 0 ]] && usage -h "Incorrect number of arguments."
shiftArgs=$shiftArgs-1
shift
done
done
set -u
#==============================================================================
# Command Line Verification
[[ -n "${Log:-}" ]] || \
Log="$RootHome/${ThisScript%.sh}.$(date +'%Y%m%d-%H%M%S').log"
[[ "$LimitedSudoers" -eq 2 ]] &&
usage -h "Specify either '-limited' (limited sudoers) for '-full' (full sudoers)."
#==============================================================================
# Main Program
trap terminate EXIT SIGINT SIGTERM
if [[ "$Log" != off ]]; then
touch "$Log" || bail "Couldn't touch log file [$Log]."
# Redirect stdout and stderr to a log file.
exec > >(tee "$Log")
exec 2>&1
msg "${H1}\\nLog is: $Log"
fi
ThisUser=$(id -n -u)
msg "Starting $ThisScript v$Version as $ThisUser@${HOSTNAME%%.*} on $(date)."
[[ "$ThisUser" == root ]] || bail "Run this as root, not $ThisUser."
# Determine list of SDP instances based in /p4/*/root dirs.
i=0
# shellcheck disable=SC2012
for SDPInstance in $(ls -d /p4/*/logs/ 2>/dev/null|cut -d '/' -f 3); do
P4ServiceList[i]="p4d_${SDPInstance}"
i+=1
P4ServiceList[i]="p4broker_${SDPInstance}"
i+=1
P4ServiceList[i]="p4p_${SDPInstance}"
i+=1
P4ServiceList[i]="p4dtg_${SDPInstance}"
i+=1
done
SystemCtlCmdList[0]="start"
SystemCtlCmdList[1]="stop"
SystemCtlCmdList[2]="restart"
SystemCtlCmdList[3]="status"
SystemCtlCmdList[4]="cat"
SystemCtlCmdList[5]="enable"
SystemCtlCmdList[6]="disable"
SystemCtlCmdList[7]="is-enabled"
SystemCtlPath="$(command -v systemctl)"
LslocksPath="$(command -v lslocks)"
GetcapPath="$(command -v getcap)"
SetcapPath="$(command -v setcap)"
OSUSER="$(stat -c %U /p4/common 2>/dev/null)"
[[ -n "$OSUSER" ]] || bail "Could not owner of /p4/common. Aborting."
TempSudoersFile=$(mktemp)
LiveSudoersFile="/etc/sudoers.d/$OSUSER"
BackupSudoersFile="$RootHome/etc_sudoers.d_$OSUSER.bak.$(date +'%Y-%m-%d-%H%M%S')"
if [[ "$LimitedSudoers" -eq 1 ]]; then
{
echo "Cmnd_Alias P4_SVC = \\"
for Service in node_exporter p4prometheus; do
for SystemCtlCmd in "${SystemCtlCmdList[@]}"; do
echo " $SystemCtlPath $SystemCtlCmd $Service, \\"
done
done
for P4Service in "${P4ServiceList[@]}"; do
for SystemCtlCmd in "${SystemCtlCmdList[@]}"; do
echo " $SystemCtlPath $SystemCtlCmd $P4Service, \\"
done
done
echo " /opt/perforce/helix-sdp/sdp/Server/Unix/p4/common/sdp_upgrade/sdp_upgrade.sh, \\"
echo " $GetcapPath, \\"
echo " $SetcapPath, \\"
echo -e " $LslocksPath\\n"
echo "$OSUSER $HOSTNAME = (root) NOPASSWD: P4_SVC"
} > "$TempSudoersFile"
else
echo "perforce ALL=(ALL) NOPASSWD: ALL" > "$TempSudoersFile"
fi
msg "Generated limited sudoers temp file:\\n${H2}\\n$(cat "$TempSudoersFile")\\n${H2}\\n"
if [[ "$NoOp" -eq 0 ]]; then
if [[ -r "$LiveSudoersFile" ]]; then
msg "This file already exists: $LiveSudoersFile:\\n${H2}\\n$(cat "$LiveSudoersFile")\\n${H2}\\n"
if diff -q "$TempSudoersFile" "$LiveSudoersFile" > /dev/null 2>&1; then
msg "\\nVerified: The new generated sudoers file matches the currently installed one.\\nNo further processing required.\\n"
exit 0
fi
if [[ "$Force" -eq 1 ]]; then
msg "\\nOverwriting existing live sudoers file due to -f."
msg "Creating backup file [$BackupSudoersFile]."
cp -p "$LiveSudoersFile" "$BackupSudoersFile" ||\
bail "Aborting: Failed to do: cp -p \"$LiveSudoersFile\" \"$BackupSudoersFile\""
else
bail "Aborting because sudoers file already exists (displayed above).\\n\\nUse '-f' to replace this file with the newly generated one."
fi
fi
msg "Installing $LiveSudoersFile"
mv -f "$TempSudoersFile" "$LiveSudoersFile" ||\
bail "Failed to move generated temp file in place as: $LiveSudoersFile"
chmod 0400 "$LiveSudoersFile" ||\
bail "Failed to do: chmod 0400 \"$LiveSudoersFile\""
TmpFile=$(mktemp)
# shellcheck disable=SC2024
if sudo -l -U "$OSUSER" > "$TmpFile" 2>&1; then
if grep -q 'syntax error' "$TmpFile"; then
errmsg "The newly generated file failed a syntax check. Rolling back to prior version."
mv -f "$BackupSudoersFile" "$LiveSudoersFile" ||\
bail "Failed to do: mv -f \"$BackupSudoersFile\" \"$LiveSudoersFile\""
bail "Aborted after successfull rollback to earlier version of [$LiveSudoersFile]."
else
msg "Newly generated sudoers file passed a syntax check."
fi
else
bail "Could not test generated sudoers file. If needed, the backup file is: $BackupSudoersFile"
fi
msg "\\nNew sudoers file successfully installed.\\n"
else
msg "\\nBecause -y was not specified, not installing generated file as: $LiveSudoersFile"
fi
rm -f "$TempSudoersFile"
exit "$ErrorCount"
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #9 | 31716 | C. Thomas Tyler |
Added support for 'p4 monitor -L'. Changes: * Added 'sudo /usr/bin/lsof' to sudoers commands. * Added 'monitor.lsof=sudo /usr/bin/lsof -F pln' as configurable in configurables.cfg, so that 'ccheck.sh' (the best practice configurables check) will search for it. * Updated configure_new_server.sh to set this value. * Added 'lsof' so list of SDP standard pacakges in install_sdp.sh. Note that 'sudo' is reliably found in /usr/bin/lsof across Linux distros, so this was deemed safe to hard-code. Fixes: SDP-1265 (Feature): Support 'p4 monitor -L' by default. |
||
| #8 | 31445 | C. Thomas Tyler |
Removed setcap handling code from upgrade.sh now that we can rely on AmbientCapabilities in the systemd unit file to enable the OOM Killer defense feature in p4d. Thus, the limited sudoers no longer requires setcap/getcap commands. Fixed various typos with aspell. #review-31446 |
||
| #7 | 31313 | C. Thomas Tyler |
Added '.service' alternatives for all systemctl services, because command completion on some platforms appends the '.service' suffix, and the sudo entries will not work unless '.service' is listed. So for example, now both of these will work: $ sudo systemctl start p4d_1 $ sudo systemctl start p4d_1.service Added support for the opt_perforce_sdp_backup service and timer, including adding explicit support for enabling and disabling the timer. |
||
| #6 | 31303 | C. Thomas Tyler |
Fixed issue where invalid sudoers file could be generated if setcap and getcap are not available, e.g. on SuSE 15 systems. Added helix-auth to standard list of managed services. |
||
| #5 | 31071 | C. Thomas Tyler | Refined man page output. | ||
| #4 | 31068 | C. Thomas Tyler |
Adjusted to avoid using HOME for root; using /root as a fixed value. |
||
| #3 | 30944 | ftp |
Added full path to sdp_upgrade.sh in immutable area to list of scripts that can be called with limited sudo. |
||
| #2 | 30782 | C. Thomas Tyler |
Added new install_sdp.sh script and supporting documentation. The new install_sdp.sh makes SDP independent of the separate Helix Installer software (the reset_sdp.sh script). The new script greatly improves the installation experience for new server machines. It is ground up rewrite of the reset_sdp.sh script. The new script preserves the desired behaviors of the original Helix Installer script, but is focused on the use case of a fresh install on a new server machine. With this focus, the scripts does not have any "reset" logic, making it completely safe. Added various files and functionalityfrom Helix Installer into SDP. * Added firewalld templates to SDP, and added ufw support. * Improved sudoers generation. * Added bash shell templates. This script also installs in the coming SDP Package structure. New installs use a modified SDP structure that makes it so the /p4/sdp and /p4/common now point to folders on the local OS volume rather than the /hxepots volume. The /hxdepots volume, which is often NFS mounted, is still used for depots and checkpoints, and for backups. The new structure uses a new /opt/perforce/helix-sdp structure under which /p4/sdp and /p4/common point. This structure also contains the expaneded SDP tarball, downloads, helix_binaries, etc. This change represents the first of 3-phase rollout of the new package structure. In this first phase, the "silent beta" phase, the new structure is used for new installations only. This phase requires no changes to released SDP scripts except for mkdirs.sh, and even that script remains backward-compatible with the old structure if used independently of install_sdp.sh. If used with install_sdp.sh, the new structure is used. In the second phase (targeted for SPD 2024.2 release), the sdp_upgrade.sh script will convert existing installations to the new structure. In the third phase (targeted for SDP 2025.x), this script will be incorporated into OS pacakge installations for the helix-sdp package. Perforce internal wikis have more detail on this change. #review-30783 |
||
| #1 | 30681 | C. Thomas Tyler |
Added gen_sudoers.sh script to generate a sudoers file for perforce OSUSER. This generates a more secure limited sudoers file. Previously, adding a sudoers entry for the OSUSER (usually 'perforce') was done only by the Helix Installer. In the Helix Installer variant, a single "one-size-filts-all" sudoers file was used, with the following characteristics: * The instances for Helix Core services were referenced with a '*' wildcard to match all SDP instances, which has since been determined to introduce a vulnerability. In this new script, the wildcard is replaced with separate entries for each SDP instance. * There were entries for all known paths of utilities like lslocks, setcap, and getcap. This new script generates the correct path valid for the current machine. With this change, the functionality will be available in the SDP directly. This new gen_sudoers.sh script can be called by mkdirs.sh directly to update the sudoers file each time a new SDP instance is added, if the new '-fs' (full sudo) or '-ls' (limited sudoers) entries are used. There is no change to the default behavior of mkdirs.sh; only a change if new options are utilized. This script comes with docs and examples for the new script as well as doc changes for mkdirs.sh. (Also added missing documentation for the '-no_enable' option). Further changes needed: * Add doc reference in SDP_Guide.Unix.adoc |