gen_sudoers.sh #15

  • //
  • guest/
  • perforce_software/
  • sdp/
  • dev/
  • Server/
  • Unix/
  • setup/
  • gen_sudoers.sh
  • View
  • Commits
  • Open Download .zip Download (17 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
#------------------------------------------------------------------------------

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

declare ThisScript=${0##*/}
declare Version=1.5.1
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 -a P4InstanceList
declare -a SudoAliasList
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: emit_sudoers_cmd_alias
#
# Emit one sudoers Cmnd_Alias from the supplied command list. This intentionally
# keeps generated aliases smaller and logically grouped for compatibility with
# both classic sudo and sudo-rs.
function emit_sudoers_cmd_alias
{
   local aliasName=${1:-}
   shift || true
   local -a cmdList=("$@")
   local -i cmdCount=${#cmdList[@]}
   local -i cmdIndex=0
   local -i lastIndex=0

   [[ -n "$aliasName" ]] || bail "emit_sudoers_cmd_alias called without an alias name."
   [[ "$cmdCount" -gt 0 ]] || return 0

   echo "Cmnd_Alias $aliasName = \\"
   lastIndex=$((cmdCount - 1))

   for cmdIndex in "${!cmdList[@]}"; do
      if [[ "$cmdIndex" -lt "$lastIndex" ]]; then
         echo "   ${cmdList[$cmdIndex]}, \\"
      else
         echo -e "   ${cmdList[$cmdIndex]}\n"
      fi
   done

   SudoAliasList+=("$aliasName")
}

#------------------------------------------------------------------------------
# Function: emit_sudoers_user_spec
#
# Emit the final user specification that references the generated Cmnd_Alias
# definitions.
function emit_sudoers_user_spec
{
   local osUser=${1:-}
   local hostName=${2:-}
   local -i aliasCount=${#SudoAliasList[@]}
   local -i aliasIndex=0
   local -i lastIndex=0

   [[ -n "$osUser" ]] || bail "emit_sudoers_user_spec called without an OS user."
   [[ -n "$hostName" ]] || bail "emit_sudoers_user_spec called without a host name."
   [[ "$aliasCount" -gt 0 ]] || bail "No sudoers command aliases were generated."

   echo "$osUser $hostName,localhost = (root) NOPASSWD: \\"
   lastIndex=$((aliasCount - 1))

   for aliasIndex in "${!SudoAliasList[@]}"; do
      if [[ "$aliasIndex" -lt "$lastIndex" ]]; then
         echo "   ${SudoAliasList[$aliasIndex]}, \\"
      else
         echo "   ${SudoAliasList[$aliasIndex]}"
      fi
   done
}

#------------------------------------------------------------------------------
# Function: validate_sudoers_file
#
# Validate a sudoers file with the active visudo implementation. On Ubuntu 26+
# this is commonly visudo-rs; on older platforms this is classic visudo.
function validate_sudoers_file
{
   local sudoersFile=${1:-}
   local validationOutput=

   [[ -n "$sudoersFile" ]] || bail "validate_sudoers_file called without a file name."
   [[ -r "$sudoersFile" ]] || bail "Cannot read sudoers file for validation: $sudoersFile"

   validationOutput=$(mktemp)

   if visudo -c -f "$sudoersFile" > "$validationOutput" 2>&1; then
      rm -f "$validationOutput"
      return 0
   else
      cat "$validationOutput"
      rm -f "$validationOutput"
      return 1
   fi
}

#------------------------------------------------------------------------------
# Function: terminate
# shellcheck disable=SC2329
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/*/logs dirs.
i=0
# shellcheck disable=SC2012
for SDPInstance in $(ls -d /p4/*/logs/ 2>/dev/null|cut -d '/' -f 3); do
   P4InstanceList[i]="$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 -Lc %U /p4/common 2>/dev/null)"
[[ -n "$OSUSER" ]] || bail "Could not owner of /p4/common. Aborting."
[[ "$OSUSER" == root ]] && \
   bail "Owner of /p4/common is unexpectedly root; it should be some other user e.g. 'perforce'. 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
   {
      declare -a ServiceCmdList
      declare -a TimerCmdList
      declare -a InstanceCmdList
      declare -a AdminCmdList
      declare -i InstanceAliasIndex=0

      SudoAliasList=()

      # General SDP-related systemd services.
      ServiceCmdList=()
      for Service in helix-auth monitor_locks node_exporter p4metrics p4prometheus vmagent; do
         for SystemCtlCmd in "${SystemCtlCmdList[@]}"; do
            ServiceCmdList+=("$SystemCtlPath $SystemCtlCmd $Service")
            ServiceCmdList+=("$SystemCtlPath $SystemCtlCmd ${Service}.service")
         done
      done
      emit_sudoers_cmd_alias P4_SVC_SYSTEMD_SERVICES "${ServiceCmdList[@]}"

      # Timer-backed SDP services. Keep these separate from regular services
      # because each timer-backed service has bare, .service, and .timer forms.
      TimerCmdList=()
      for TimerService in monitor_locks opt_perforce_sdp_backup; do
         for SystemCtlCmd in "${SystemCtlCmdList[@]}"; do
            TimerCmdList+=("$SystemCtlPath $SystemCtlCmd $TimerService")
            TimerCmdList+=("$SystemCtlPath $SystemCtlCmd ${TimerService}.service")
            TimerCmdList+=("$SystemCtlPath $SystemCtlCmd ${TimerService}.timer")
         done
      done
      emit_sudoers_cmd_alias P4_SVC_SYSTEMD_TIMERS "${TimerCmdList[@]}"

      # Instance-specific Helix Core services. Generate one alias per SDP
      # instance so sites with multiple instances do not create an overly large
      # comma-separated command list.
      for SDPInstance in "${P4InstanceList[@]}"; do
         InstanceAliasIndex+=1
         InstanceCmdList=()
         for P4Service in "p4d_${SDPInstance}" "p4broker_${SDPInstance}" "p4p_${SDPInstance}" "p4dtg_${SDPInstance}"; do
            for SystemCtlCmd in "${SystemCtlCmdList[@]}"; do
               InstanceCmdList+=("$SystemCtlPath $SystemCtlCmd $P4Service")
               InstanceCmdList+=("$SystemCtlPath $SystemCtlCmd ${P4Service}.service")
            done
         done
         emit_sudoers_cmd_alias "P4_SVC_INSTANCE_${InstanceAliasIndex}" "${InstanceCmdList[@]}"
      done

      # SDP administrative commands.
      AdminCmdList=("/opt/perforce/helix-sdp/sdp/Server/Unix/p4/common/sdp_upgrade/sdp_upgrade.sh")
      [[ -n "$LslocksPath" ]] && AdminCmdList+=("$LslocksPath")
      [[ -n "$LsofPath" ]] && AdminCmdList+=("$LsofPath")
      emit_sudoers_cmd_alias P4_SVC_ADMIN_TOOLS "${AdminCmdList[@]}"

      emit_sudoers_user_spec "$OSUSER" "$HOSTNAME"

   } > "$TempSudoersFile"
else
   echo "$OSUSER ALL=(ALL) NOPASSWD: ALL" > "$TempSudoersFile"
fi

msg "Generated limited sudoers temp file:\n${H2}\n$(cat "$TempSudoersFile")\n${H2}\n"

if validate_sudoers_file "$TempSudoersFile"; then
   msg "Generated sudoers temp file passed validation with active visudo."
else
   bail "Generated sudoers temp file failed validation with active visudo."
fi

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
         if validate_sudoers_file "$LiveSudoersFile"; then
            msg "\nVerified: The new generated sudoers file matches the currently installed one, and the installed file passes validation with active visudo.\nNo further processing required.\n"
            exit 0
         else
            bail "Installed sudoers file matches the generated file but fails validation with active visudo: $LiveSudoersFile"
         fi
      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\""

   if validate_sudoers_file "$LiveSudoersFile"; then
      msg "Installed sudoers file passed validation with active visudo."
   else
      errmsg "Installed sudoers file failed validation with active visudo. Rolling back to prior version if available."
      if [[ -r "$BackupSudoersFile" ]]; then
         mv -f "$BackupSudoersFile" "$LiveSudoersFile" ||\
            bail "Failed to do: mv -f \"$BackupSudoersFile\" \"$LiveSudoersFile\""
         bail "Aborted after successful rollback to earlier version of [$LiveSudoersFile]."
      else
         rm -f "$LiveSudoersFile"
         bail "Aborted after removing invalid newly generated sudoers file [$LiveSudoersFile]."
      fi
   fi

   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
#16 33022 C. Thomas Tyler Simplify usage of 'force_start' option with systemd.

This entailed enhancing docs for gen_sudoers.sh (no functional change), as
well as adding documentation about force starts, p4d_init.log, and documentation
of the journal corruption detection handling.

Minor style consistency changes were made in p4broker_base and p4p_base.

Fixes SDP-837.

#review-33023 @jclucas @mark_zinthefer @robert_cowham @tom_tyler
#15 32942 C. Thomas Tyler gen_sudoers.sh v1.5.1

* Changed OSUSER detection from:

  stat -c %U /p4/common

to:

  stat -Lc %U /p4/common

so ownership is determined from the SDP common directory target
rather than from the /p4/common symlink itself.

* Improves compatibility on platforms where symbolic-link ownership
differs from target-directory ownership.
#14 32941 C. Thomas Tyler Fixed an edge case where a broken SDP install with root-owned /p4/common
would cause gen_sudoers.sh to generate a sudoers file for root rather
than the appropriate OS user.

Added ShellCheck silender for terminate() function (called by trap).

Cosmetic code fix for newlines (introduced due to ShellCheck bug).
#13 32898 C. Thomas Tyler Updated gen_sudoers.sh to add monitor_locks.{service,timer}.

#review-32899 @robert_cowham @tom_tyler
#12 32440 C. Thomas Tyler Appended ",localhost" to sudoers entry to make it more robust.

Fixes SDP-1342.
#11 32376 C. Thomas Tyler Added 'vmagent' service, part of P4Prometheus (for P4RA customers).
#10 32345 C. Thomas Tyler Added sudo entry for p4metrics in gen_sudoers.sh.

  #review @robert_cowham
#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