enable_sso_for_ap_users.sh #1

  • //
  • guest/
  • tom_tyler/
  • sw/
  • main/
  • SSO_Cutover/
  • enable_sso_for_ap_users.sh
  • View
  • Commits
  • Open Download .zip Download (15 KB)
#!/bin/bash
set -u

# For usage, run: enable_sso_for_ap_users.sh -man

#==============================================================================
# Declarations and Environment

declare ThisScript=${0##*/}
declare Version=1.0.7
declare ThisUser=
declare Args="$*"
declare CmdLine="$0 $Args"
declare ThisHost=${HOSTNAME%%.*}
declare -i Debug=${SDP_DEBUG:-0}
declare -i ErrorCount=0
declare -i WarningCount=0
declare -i SilentMode=0
declare -i Found=0
declare Log=
declare H1="=============================================================================="
declare H2="------------------------------------------------------------------------------"
declare SDPInstance=${SDP_INSTANCE:-}
declare SDPRoot=${SDP_ROOT:-/p4}
declare SDPCommon="$SDPRoot/common"
declare SDPCommonBin="$SDPCommon/bin"
#declare SDPCommonLib="$SDPCommon/lib"
declare SDPCommonLib=/p4/common/site/sso_cutover
declare SDPEnv="$SDPCommonBin/p4_vars"
declare UserListFile=
declare NewUserListFile=
declare UserSpecFile=
declare PasswordFile=
declare Password=
declare -a UsersToIgnore
declare -i UsersToIgnoreCount=0

# Color support for interactive operation.
declare GREEN=
declare RED=
declare YELLOW=
declare RESET=

declare -i NoOp=0

#==============================================================================
# Local Functions
function msg () { echo -e "$*"; }
function msgn () { echo -e -n "$*"; }
function msg_green  { msg "${GREEN}$*${RESET}"; }
function msg_yellow { msg "${YELLOW}$*${RESET}"; }
function msg_red    { msg "${RED}$*${RESET}"; }
function dbg () { [[ "$Debug" -eq 0 ]] || msg "DEBUG: $*"; }
function dbg2 () { [[ "$Debug" -lt 2 ]] || msg "DEBUG: $*"; }
function errmsg () { msg_red "\nError: ${1:-Unknown Error}\n"; ErrorCount+=1; }
function warnmsg () { msg_yellow "\nWarning: ${1:-Unknown Warning}\n"; WarningCount+=1; }
function bail () { errmsg "${1:-Unknown Error}"; exit "${2:-1}"; }

function show_users_in_group () {
   local group="$1"
   local -a users=

   # Global array of users to ignore.
   UsersToIgnore=()

   # Local reusable array of users in a group.
   users=()

   # Extract Users: section safely
   while IFS= read -r line; do
      # Stop when we hit a non-indented line after Users:
      [[ "$line" =~ ^[^[:space:]] ]] && break

      # Skip blank lines
      [[ -z "$line" ]] && continue

      # Trim leading whitespace
      user="${line#"${line%%[![:space:]]*}"}"

      users+=("$user")
   done < <(
      "$P4BIN" group -o "$group" | awk '
         /^Users:/ {in_users=1; next}
         in_users && /^[^[:space:]]/ {exit}
         in_users {print}
   '
   )

   printf '%s\n' "${users[@]}"
}

#------------------------------------------------------------------------------
# This function updates the globals UsersToIgnore and UsersToIgnoreCount.
function build_list_of_ignored_users () {
   local extensionSpecFile=
   local group=
   local groupList=
   local user=
   local userList=

   extensionSpecFile="$(mktemp "${P4TMP}/p4as.XXXXXX.extension.p4s")"
   dbg "Running: $P4BIN extension --configure Auth::loginhook --name loginhook-a1 -o .GT. $extensionSpecFile"
   if "$P4BIN" extension --configure Auth::loginhook --name loginhook-a1 -o > "$extensionSpecFile"; then
      dbg2 "Extension spec file is: $extensionSpecFile"

      groupList=$(grep -A1 'non-sso-groups:' "$extensionSpecFile"|tail -1)
      #shellcheck disable=SC2086 disable=SC2116
      groupList=$(echo $groupList)
      userList=$(grep -A1 'non-sso-users:' "$extensionSpecFile"|tail -1)
      #shellcheck disable=SC2086 disable=SC2116
      userList=$(echo $userList)

      dbg2 "SSO-exempt group list: [$groupList]"
      dbg2 "SSO-exempt user list: [$userList]"
      for group in $groupList; do
         for user in $(show_users_in_group "$group"); do
            dbg2 "Adding user $user from group $group to list of users to ignore."
            UsersToIgnore[UsersToIgnoreCount]="$user"
            UsersToIgnoreCount+=1
         done
      done

      for user in $userList; do
         dbg2 "Adding user $user to list of users to ignore."
         UsersToIgnore[UsersToIgnoreCount]="$user"
         UsersToIgnoreCount+=1
      done

      dbg "There are ${#UsersToIgnore[@]} users to ignore."
      [[ "$Debug" -eq 0 ]] && rm -f "$extensionSpecFile"
   else
      errmsg "Could not read P4AS extension config."
      return 1
   fi

   return 0
}

#==============================================================================
# Load SDP Library Functions.

if [[ -d "$SDPCommonLib" ]]; then
   # shellcheck disable=SC1090 disable=SC1091
   source "$SDPCommonLib/logging.lib" ||\
      bail "Failed to load bash lib [$SDPCommonLib/logging.lib]. Aborting."
   # shellcheck disable=SC1090 disable=SC1091
   source "$SDPCommonLib/run.lib" ||\
      bail "Failed to load bash lib [$SDPCommonLib/run.lib]. Aborting."
fi

#------------------------------------------------------------------------------
# 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 a
# 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 "Missing required parameter <RequiredParameter>."
#------------------------------------------------------------------------------
function usage
{
   local style=${1:--h}
   local usageErrorMessage=${2:-Unset}

   if [[ "$usageErrorMessage" != Unset ]]; then
      msg "\n\nUsage Error:\n\n$usageErrorMessage\n\n"
   fi

   msg "USAGE for $ThisScript v$Version:

$ThisScript [-i <instance>] [-si] [-n] [-d|-d2|-D]

or

$ThisScript [-h|-man|-V]
"
   if [[ $style == -man ]]; then
      msg "
DESCRIPTION:
	This script enables auto-provisioned users for SSO.

	Auto-provisoned users are those created with 'p4 ldapsync -c -u -U' (or
	similar).

	The SSO_default.sh trigger script enables SSO for manually created users;
	this script does the same for auto-provisioned users.

	This is intended to be run occaisonally in cron, perhaps every half hour
	with weekly log cleanup, with crontab entries like examples:

	0,30 * * * * [ -e /p4/common/bin ] && /p4/common/bin/run_if_master.sh 1 /p4/common/site/sso_cutover/enable_sso_for_ap_users.sh -si
	15 * * * 6 [ -e /p4/common/bin ] && /p4/common/bin/run_if_master.sh 1 rm -f /p4/1/logs/enable_sso_for_ap_users.log

	This crontab is installed by the SSO_Cutover.sh script.

	This script appends to this same log each time it runs:
	$LOGS/${ThisScript%.sh}.log

	An optional sample crontab wipes the log each week.

OPTIONS:
 -i <sdp_instance>
	Specify the SDP Instance.  If not specified, the \$SDP_INSTANCE variable is
	referenced, otherwise the default is '1'.

 -si
	Operate silently.  All output (stdout and stderr) are redirected to the log
	only; no output appears on the terminal (except for help/usage info, usage
	errors, or version checks on startup).  The '-si' option cannot be used with
	'-L off'.
      
	This is intended to be used when running scripts from the crontab.  By
	preventing any output, it prevents email from being sent by cron daemon
	directly, as cron does when a script called from cron generates any output.
	This script is then responsible for email handling or other notifications, if
	any is to be done.

 -n
	Enable DRY RUN/Preview mode, displaying commands that would affect data
	rather than executing them.

 -d	Enable higher verbosity for debugging.

 -d2	Enable even higher verbosity for debugging than '-d.

 -D
	Set extreme debugging verbosity using bash 'set -x' mode.

HELP OPTIONS:
 -h	Display short help message.
 -man	Display man-style help message.
 -V	Display version info for this script.

LOGGING:
	All output (stdout and stderr) goes to:
	$LOGS/${ThisScript%.sh}.log

	NOTE: This script is self-logging.  That is, output displayed on the screen
	is simultaneously captured in the log file.  Using redirection operators like
   like '> log' or '2>&1' or using 'tee' are unnecessary (but harmless).

FILES:
	Persistent log: $LOGS/${ThisScript%.sh}.log

EXAMPLES:
	Example 1: Manual preview usage:

	${ThisScript} -n

	Example 2: Crontab usage:
	${ThisScript} -si

SEE ALSO:
	See /p4/common/site/bin/SSO_default.sh
	See /p4/common/site/sso_cutover/SSO_Cutover.sh
"
   fi

   exit 2
}

#==============================================================================
# Command Line Processing

declare -i ShiftArgs=0

set +u
while [[ $# -gt 0 ]]; do
   case $1 in
      (-h) usage -h;;
      (-man|--help) usage -man;;
      (-V|--version) msg "$ThisScript version $Version"; exit 0;;
      (-si) SilentMode=1;;
      (-n) NoOp=1;;
      (-d) Debug=1;;
      (-d2) Debug=2;;
      (-D) Debug=1; set -x;; # Use bash 'set -x' extreme debug mode.
      (-*) usage -h "Unknown option ($1).";;
      (*) usage -h "Unknown arg ($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

# shellcheck disable=SC1090
source "$SDPEnv" "$SDPInstance" ||\
   bail "Could not do: source \"$SDPEnv\" \"$SDPInstance\""

#==============================================================================
# Command Line Verification

[[ "$SilentMode" -eq 1 && "$Log" == off ]] && \
   usage -h "Cannot use '-si' with '-L off'."

#==============================================================================
# Main Program

trap terminate EXIT SIGINT SIGTERM

# Detect support for colors
if [[ $SilentMode -eq 0 ]] \
   && command -v tput >/dev/null 2>&1 \
   && [[ -t 1 ]] \
   && [[ "$(tput colors)" -ge 8 ]]; then
   RED="$(tput setaf 1)"
   GREEN="$(tput setaf 2)"
   YELLOW="$(tput setaf 3)"
   RESET="$(tput sgr0)"
else
   RED=; GREEN=; YELLOW=; RESET=
fi

if [[ "$Log" != off ]]; then
   # If $Log is not yet defined, set it to a reasonable default.
   if [[ -z "$Log" ]]; then
      [[ -d "${LOGS:-}" ]] ||\
         bail "Log directory does not exist: [${LOGS:-unset}]. Verify SDP_ROOT [$SDPRoot] and SDP instance [$SDPInstance] are correct."
      Log="$LOGS/${ThisScript%.sh}.log"
   fi

   Log="$LOGS/${ThisScript%.sh}.log"

   # Redirect stdout and stderr to a log file.  When redirecting to a log,
   # suppress color codes because they detract from readability in logs.
   if [[ "$SilentMode" -eq 0 ]]; then
      if [[ -n "$GREEN" ]]; then
         exec > >( tee -a \
            >(sed -r \
            -e 's/\x1B\[[0-9;]*[a-zA-Z]//g' \
            -e 's/\x1B\(B//g' >>"$Log"))
      else
         exec > >(tee -a "$Log")
      fi
      exec 2>&1
   else
      exec >>"$Log"
      exec 2>&1
   fi

   msg "${H1}\nLog is: $Log\n"
fi

ThisUser=$(id -n -u)
msg "Starting $ThisScript v$Version as $ThisUser@$ThisHost on $(date) with \\n$CmdLine"

if [[ "$NoOp" -eq 0 ]]; then
   msg "\nOperating in Live Operation mode."
else
   msg "\nOperating in DRY RUN (Preview) mode."
fi

# The User List file is an optimization.  We create the user list file if it doesn't exist,
# and it persists between runs on any given machine. (After a failover, it'll just get
# recreated on the next run of this script).

UserListFile="${P4TMP}/${ThisScript%.sh}.user_list.txt"

if [[ -r "$UserListFile" ]]; then
   msg "Prior user list file is: $UserListFile"
else
   if "$P4BIN" -ztag -F %User% users > "$UserListFile"; then
      msg "Generated user list file: $UserListFile"
      warnmsg "No further processing due to lack of pre-existing user list file. On the next run, checks will commence."
      exit 0
   else
      # Don't leave incomplete cruft text around.
      rm -f "$UserListFile"
      bail "Failed to generate user list file '$UserListFile'."
   fi
fi

NewUserListFile="$(mktemp "${P4TMP}/new_user_list.XXXXXXX.txt")"
if "$P4BIN" -ztag -F %User% users > "$NewUserListFile"; then
   msg "Generated new user list file: $NewUserListFile"
else
   # Don't leave incomplete cruft text around.
   rm -f "$NewUserListFile"
   bail "Failed to generate new user list file '$NewUserListFile'."
fi

build_list_of_ignored_users ||\
   bail "Failed to build list of exempt users from P4AS extension info."

while read -r User; do
   if grep -q -E "^$User$" "$UserListFile"; then
      dbg2 "Ignoring pre-existing user $User."
   else
      dbg2 "Possible new user detected: [$User]"
      UserSpecFile="$(mktemp "${P4TMP}.user.XXXXXXX.p4s")"
      if "$P4BIN" -ztag -F %AuthMethod%:%User% user --exists -o "$User" > "$UserSpecFile"; then
         if grep -q ^ldap: "$UserSpecFile"; then
            dbg2 "AuthMethod for new user [$User] is LDAP. Checking SSO-exempt user list."
            Found=0
            for u in "${UsersToIgnore[@]}"; do
               if [[ "$u" == "$User" ]]; then
                  Found=1
                  break
               fi
            done
            if [[ "$Found" -eq 1 ]]; then
               dbg2 "User [$User] is exempt from SSO; ignoring."
            else
               dbg "Detected new auto-provisioned user [$User]. Fixing ..."
               if [[ "$NoOp" -eq 0 ]]; then
                  "$P4BIN" --field AuthMethod=perforce user -o "$User" | p4 user -fi
               else
                  msg "NO_OP: Would do: $P4BIN --field AuthMethod=perforce user -o $User | p4 user -fi"
               fi

               PasswordFile=$(mktemp)
               Password=$(uuidgen)

               if echo -e "$Password\\n$Password" > "$PasswordFile"; then
                  if "$P4BIN" passwd "$User" < "$PasswordFile"; then
                     msg "SSO user [$User] now has unusable P4PASSWD."
                  else
                     errmsg "Failed to set UUID P4PASSWD for user [$User]."
                  fi
               else
                  errmsg "Failed to create temp password file for user [$User]."
               fi

               rm -f "$PasswordFile"
            fi
         else
            dbg2 "AuthMethod for new user [$User] is not LDAP. Ignoring this user."
         fi
         [[ "$Debug" -eq 0 ]] && rm -f "$UserSpecFile"
      else
         errmsg "Unable to extract user spec for user [$User]."
         [[ "$Debug" -eq 0 ]] && rm -f "$UserSpecFile"
      fi
   fi
done < "$NewUserListFile"

if [[ "$ErrorCount" -eq 0 ]]; then
   msg "\nUpdating user list file with latest users: $UserListFile"

   if [[ "$NoOp" -eq 0 ]]; then
      mv -f "$NewUserListFile" "$UserListFile" ||\
         errmsg "Failed to update user list file. This command failed:\n\tmv -f \"$NewUserListFile\" \"$UserListFile\""
   else
      msg "NO_OP: Would do: mv -f \"$NewUserListFile\" \"$UserListFile\""
   fi
fi

[[ "$Debug" -eq 0 && -r "$NewUserListFile" ]] && rm -f "$NewUserListFile"

if [[ "$ErrorCount" -eq 0 && "$WarningCount" -eq 0 ]]; then
   msg_green "${H2}\nAll processing completed successfully.\n"
elif [[ "$ErrorCount" -eq 0 ]]; then
   warnmsg "${H2}\nProcessing completed with no errors, but there were $WarningCount warnings. Review the above output carefully.\n"
else
   errmsg "${H2}\nProcessing completed, but there were $ErrorCount errors and $WarningCount warnings.  Scan above output carefully.\n" 
fi

# Illustrate using $SECONDS to display runtime of a script.
msg "Time: That took $((SECONDS/3600)) hours $((SECONDS%3600/60)) minutes $((SECONDS%60)) seconds.\n"

# The terminate() function (defined in logging.lib) handles the actual exit,
# printing the final log path and removing the EXIT trap before exiting.
exit "$ErrorCount"
# Change User Description Committed
#1 32665 C. Thomas Tyler Updated latest updates from after QA testing was completed and validation in a live environment.