gen_sudoers.sh #4

  • //
  • p4-sdp/
  • dev_rebrand/
  • Server/
  • Unix/
  • setup/
  • gen_sudoers.sh
  • View
  • Commits
  • Open Download .zip Download (13 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://workshop.perforce.com/view/p4-sdp/main/LICENSE
#------------------------------------------------------------------------------

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

# Version ID Block. Relies on +k filetype modifier.
#------------------------------------------------------------------------------
# shellcheck disable=SC2016
declare VersionID='$Id: //p4-sdp/dev_rebrand/Server/Unix/setup/gen_sudoers.sh#4 $ $Change: 31723 $'
declare VersionStream=${VersionID#*//}; VersionStream=${VersionStream#*/}; VersionStream=${VersionStream%%/*};
declare VersionCL=${VersionID##*: }; VersionCL=${VersionCL%% *}
declare Version=${VersionStream}.${VersionCL}
[[ "$VersionStream" == r* ]] || Version="${Version^^}"

declare ThisScript=${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 version $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 P4 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 version $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_upgrades.sh (always), lslocks (if available), and lsof (if available).
      # Make sure the last entry does not end with a comma.
      if [[ -z "$LslocksPath" && -z "$LsofPath" ]]; then
         echo -e "   /opt/perforce/helix-sdp/sdp/Server/Unix/p4/common/sdp_upgrade/sdp_upgrade.sh\\n"
      elif [[ -n "$LslocksPath" && -n "$LsofPath" ]]; then
         echo "   /opt/perforce/helix-sdp/sdp/Server/Unix/p4/common/sdp_upgrade/sdp_upgrade.sh, \\"
         echo -e "   $LslocksPath, \\"
         echo -e "   $LsofPath\\n"
      elif [[ -n "$LslocksPath" ]]; then
         echo -e "   /opt/perforce/helix-sdp/sdp/Server/Unix/p4/common/sdp_upgrade/sdp_upgrade.sh, \\"
         echo -e "   $LslocksPath\\n"
      else # If lsof is available; lslocks is not.
         echo -e "   /opt/perforce/helix-sdp/sdp/Server/Unix/p4/common/sdp_upgrade/sdp_upgrade.sh, \\"
         echo -e "   $LsofPath\\n"
      fi

      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
#4 31723 C. Thomas Tyler Merge Down dev -> dev_rebrand.
#3 31617 C. Thomas Tyler Merged work from dev_c2s (development) stream to sibling dev_rebrand (sparsedev) stream.
#2 31615 C. Thomas Tyler First pass at rebranding changes, including:
* Changes to remove 'swarm.' from Workshop URLS, so swarm.workshop -> workshop.
* Changed URL for Copyright.
* Renamed get_helix_binaries.sh -> get_p4_binaries.sh, with associated directory and doc changes.
* Accounted for rename of HAS -> P4AS.
* Changed HMS references to P4MS.
* Replaced "Helix" and "Helix Core" references.
* Renamed variables to reduce tech debt buildup induced by rebranding.
* Changed default mount points:
/hxdepots[-1,N] -> /p4depots[-1,N]
/hxmetadata[1,2] -> /p4db[-1,2]
/hxlogs -> /p4logs

Also made some changes related to rebranding going out with r25.1.
#1 31591 C. Thomas Tyler Populate stream //p4-sdp/dev_rebrand from //p4-sdp/dev.
//p4-sdp/dev/Server/Unix/setup/gen_sudoers.sh
#2 31574 C. Thomas Tyler Merged SDP 2024.2 Patch 4 from Classic to Streams.
p4 merge -b SDP_Classic_to_Streams
#1 31397 C. Thomas Tyler Populate -b SDP_Classic_to_Streams -s //guest/perforce_software/sdp/...@31368.
//guest/perforce_software/sdp/dev/Server/Unix/setup/gen_sudoers.sh
#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