gen_sudoers.sh #1

  • //
  • guest/
  • russell_jackson/
  • sdp/
  • Server/
  • Unix/
  • setup/
  • gen_sudoers.sh
  • View
  • Commits
  • Open Download .zip Download (15 KB)
#!/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
#------------------------------------------------------------------------------

#==============================================================================
# FORK NOTE (russell_jackson customized SDP):
# This SDP fork is INIT-BASED (SysV init scripts in
# /p4/common/etc/init.d), NOT systemd. The '-limited' sudoers generated
# below grants 'systemctl' rights (P4_SVC Cmnd_Alias). Those systemctl
# grants are harmless on an init-based host (the perforce user simply
# won't invoke systemctl) and are RETAINED so they are immediately
# relevant if/when this fork adopts systemd. No systemd-specific
# functionality has been removed.
#
# Also note: this fork hardcodes SDP_ROOT=/p4, so any '${SDP_ROOT:-/p4}'
# default is correct. The fork does NOT ship sdp_upgrade.sh; the grant
# for it is guarded below (see "FORK NOTE: sdp_upgrade").
#------------------------------------------------------------------------------

#==============================================================================
# Declarations and Environment
set -u

declare ThisScript=${0##*/}
declare Version=1.3.0
declare ThisUser=
declare LiveSudoersFile=
declare TempSudoersFile=
declare BackupSudoersFile=
declare SDPInstance=
declare P4Service=
declare SystemCtlCmd=
declare SystemCtlPath=
declare LslocksPath=
declare LsofPath=
declare TmpFile=
declare -i LimitedSudoers=2
declare -a SystemCtlCmdList
declare Service=
declare TimerService=
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 provided.

	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 overwrite 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 because 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) RootHome=/root; usage -h;;
      (-man) RootHome=/root; 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)"
LsofPath="$(command -v lsof)"

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 helix-auth node_exporter p4prometheus; do
         for SystemCtlCmd in "${SystemCtlCmdList[@]}"; do
            echo "   $SystemCtlPath $SystemCtlCmd $Service, \\"
            echo "   $SystemCtlPath $SystemCtlCmd ${Service}.service, \\"
         done
      done

      ### Remove this shellcheck after more timer services are added to SDP.
      # shellcheck disable=SC2043
      for TimerService in opt_perforce_sdp_backup; do
         for SystemCtlCmd in "${SystemCtlCmdList[@]}"; do
            echo "   $SystemCtlPath $SystemCtlCmd $TimerService, \\"
            echo "   $SystemCtlPath $SystemCtlCmd ${TimerService}.service, \\"
            echo "   $SystemCtlPath $SystemCtlCmd ${TimerService}.timer, \\"
         done
      done

      for P4Service in "${P4ServiceList[@]}"; do
         for SystemCtlCmd in "${SystemCtlCmdList[@]}"; do
            echo "   $SystemCtlPath $SystemCtlCmd $P4Service, \\"
            echo "   $SystemCtlPath $SystemCtlCmd ${P4Service}.service, \\"
         done
      done

      # Add sdp_upgrade.sh (if present), lslocks (if available), and lsof (if
      # available). The last entry must not end with a comma, so the optional
      # entries are collected into an array and emitted with the trailing-comma
      # logic handled in one place (more robust than the upstream cascade).
      #
      # FORK NOTE: sdp_upgrade
      # Upstream hardcodes the OS-package path
      # /opt/perforce/helix-sdp/sdp/.../sdp_upgrade/sdp_upgrade.sh. This fork
      # installs SDP under ${SDP_ROOT:-/p4} and does NOT ship sdp_upgrade.sh.
      # The grant is therefore GUARDED: it is emitted only if the script
      # actually exists at the fork-correct path. If/when sdp_upgrade.sh is
      # added to the fork, the grant will appear automatically.
      declare SdpUpgradePath="${SDP_ROOT:-/p4}/common/sdp_upgrade/sdp_upgrade.sh"
      declare -a OptionalEntries=()
      [[ -x "$SdpUpgradePath" || -r "$SdpUpgradePath" ]] && OptionalEntries+=("$SdpUpgradePath")
      [[ -n "$LslocksPath" ]] && OptionalEntries+=("$LslocksPath")
      [[ -n "$LsofPath" ]] && OptionalEntries+=("$LsofPath")

      # The P4_SVC Cmnd_Alias requires at least one terminating entry with no
      # trailing comma; otherwise sudoers syntax is invalid. Upstream always had
      # the sdp_upgrade entry to terminate it. In this fork that entry is
      # guarded, so if none of sdp_upgrade/lslocks/lsof are present we abort
      # rather than emit an invalid sudoers file.
      [[ "${#OptionalEntries[@]}" -gt 0 ]] || \
         bail "No terminating command found for P4_SVC alias (none of sdp_upgrade.sh, lslocks, or lsof present). Refusing to write an invalid sudoers file."

      declare -i j=0
      declare -i lastIdx=$(( ${#OptionalEntries[@]} - 1 ))
      for ((j=0; j<${#OptionalEntries[@]}; j++)); do
         if [[ "$j" -eq "$lastIdx" ]]; then
            # Last entry: no trailing comma.
            echo -e "   ${OptionalEntries[$j]}\\n"
         else
            echo "   ${OptionalEntries[$j]}, \\"
         fi
      done

      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 successful 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
#1 32803 Russell C. Jackson (Rusty) Modernize russell_jackson SDP fork from upstream 2025.2.

- Port modern p4d features: partitioned/readonly clients, upgrade-safety (p4
  storage -w / p4 upgrades polling), checkpoint/replica/edge tooling, proxy &
  broker SSL trust, modern p4login, dir-ownership preflight.
- Add scripts: get_p4_binaries.sh (renamed from helix), ccheck.sh, verify_sdp.sh,
  sdp_health_check.sh, journal_watch.sh, load_checkpoint.sh, refresh_P4ROOT,
  request_replica_checkpoint.sh, keep_offline_db_current.sh, gen_sudoers.sh, etc.
- Migrate configurables to configurables.cfg applied via ccheck.sh -fix; slim
  configure_new_server.sh to setup-only.
- upgrade.sh: dry-run default, verified clean rollback point.
- Fixes from multi-agent review (rsync byte/KB+comma, cfg field counts, version
  thresholds, etc.).

See SDP_PORT_SCOPE.md for the full manifest.