From 2dd0f0d657fda0643e48f2e2677f3881da443b52 Mon Sep 17 00:00:00 2001
From: Jimmy Maple <jmaple@splunk.com>
Date: Fri, 1 Apr 2022 10:27:34 -0400
Subject: [PATCH] Updates to pwchange scripts

- Reworked pwchange scripts to recooperate after a failed password change attempt by using a user-seed.conf to reset password
- Converted Windows pwchange script from Batch to Powershell for better flexibility
- Added customization for scripts into inputs.conf so values that are unique to an environment can be leveraged without editing the scripts directly
---
 README.md               | 17 ++++++--
 README/inputs.conf.spec | 34 +++++++++++++++
 bin/appContext.sh       |  8 ++++
 bin/pwchange.bat        | 85 ------------------------------------
 bin/pwchange.ps1        | 96 +++++++++++++++++++++++++++++++++++++++++
 bin/pwchange.sh         | 54 +++++++++++++----------
 default/app.conf        |  2 +-
 default/inputs.conf     | 11 ++++-
 8 files changed, 193 insertions(+), 114 deletions(-)
 delete mode 100755 bin/pwchange.bat
 create mode 100755 bin/pwchange.ps1

diff --git a/README.md b/README.md
index a48beba..4127c55 100644
--- a/README.md
+++ b/README.md
@@ -81,13 +81,22 @@ index = _internal
 sourcetype = pw_change:output
 interval = -1
 source = pw_change_output
+splunkUserName = admin
+newPass = auto
+oldPass = changeme
+printPass = false
 
-[script://.\bin\pwchange.bat]
+[powershell://pwchange]
 disabled = 1
 index = _internal
 sourcetype = pw_change:output
 interval = -1
 source = pw_change_output
+script = . "$SplunkHome\etc\apps\SplunkForwarderRepairKit\bin\pwchange.ps1"
+splunkUserName = admin
+newPass = auto
+oldPass = changeme
+printPass = false
 ```
 
 ## Use-Cases
@@ -136,12 +145,12 @@ https://docs.splunk.com/Documentation/Splunk/latest/ReleaseNotes/FixDatetimexml2
 ~~Windows - `dateTimeCorrect.ps1`\
 \*Nix - `dateTimeCorrect.sh`~~
 
-###### Update default 'changeme' password on Splunk Forwarders (primary installations before 7.1.0)
+###### Update local user password on Splunk Forwarders (primary installations before 7.1.0)
 Forwarders deployed before version 7.1.0 didn't require the admin password be changed upon installation. Starting at 7.1.0, the forwarders required either a user-seed file or manual input of the password during first-time run. While the REST API of the forwarder is not configured to allow POST requests until the password is changed on versions prior to 7.1.0, changing the password is still recommended.
 
-This app contains scripts for Windows and Linux forwarders that will allow either a static password or random password to be configured. By default, a random password is generated and printed into the log which is sent back to Splunk.
+The variables for the environment can be configured in inputs.conf when the app is deployed such as the Splunk username (default 'admin'), whether to automatically generate a random password or to set it explicitly (default 'auto'), the value of the old password (default 'changeme'), and whether or not to send the new password back in plain-text to Splunk (default 'false').
 
-Windows - `pwchange.bat`\
+Windows - `pwchange.ps1`\
 \*Nix - `pwchange.sh`
 
 ## Restarting the Forwarder
diff --git a/README/inputs.conf.spec b/README/inputs.conf.spec
index f2bf698..fe1dd82 100644
--- a/README/inputs.conf.spec
+++ b/README/inputs.conf.spec
@@ -3,9 +3,27 @@
 [script:<uniqueName>]
 deploymentServerUri = [string]
 * Correct URI that should be configured
+* Default value is empty
 
 deploymentClientApp = [string]
 * App name that contains the correct deploymentclient.conf configuration
+* Default value is empty
+
+splunkUserName = [string]
+* Value representing the username configured on the Universal Forwarder
+* Default value is 'admin'
+
+newPass = {auto|string}
+* Value representing either a specified password to be configured or "auto"
+* Default value is 'auto'
+
+oldPass = [string]
+* Value representing the old password configured on the Universal Forwarder
+* Default is 'changeme'
+
+printPass = {true|false}
+* Determines if the new password that is generated will be sent to _internal
+* Default value is 'false''. Change to 'true' to print password into _internal
 
 [powershell:<uniqueName>]
 deploymentServerUri = [string]
@@ -13,3 +31,19 @@ deploymentServerUri = [string]
 
 deploymentClientApp = [string]
 * App name that contains the correct deploymentclient.conf configuration
+
+splunkUserName = [string]
+* Value representing the username configured on the Universal Forwarder
+* Default value is 'admin'
+
+newPass = {auto|string}
+* Value representing either a specified password to be configured or "auto"
+* Default value is 'auto'
+
+oldPass = [string]
+* Value representing the old password configured on the Universal Forwarder
+* Default is 'changeme'
+
+printPass = {true|false}
+* Determines if the new password that is generated will be sent to _internal
+* Default value is 'false''. Change to 'true' to print password into _internal
diff --git a/bin/appContext.sh b/bin/appContext.sh
index e21ebf1..20dbe8f 100644
--- a/bin/appContext.sh
+++ b/bin/appContext.sh
@@ -10,3 +10,11 @@ if [ "${SCRIPT_NAME}" = "dsRemove.sh" ]; then
   CORRECT_DS=$(${SPLUNK_HOME}/bin/splunk btool --app=$APP_NAME inputs list script://./bin/$SCRIPT_NAME | grep deploymentServerUri | sed "s/deploymentServerUri = //")
   CORRECT_APP=$(${SPLUNK_HOME}/bin/splunk btool --app=$APP_NAME inputs list script://./bin/$SCRIPT_NAME | grep deploymentClientApp | sed "s/deploymentClientApp = //")
 fi
+
+## Capture the configuration details for changing the local password
+if [ "${SCRIPT_NAME}" = "pwchange.sh" ]; then
+  SPLUNK_USER=$(${SPLUNK_HOME}/bin/splunk btool --app=$APP_NAME inputs list script://./bin/$SCRIPT_NAME | grep newPass | sed "s/splunkUserName = //")
+  NEWPASS=$(${SPLUNK_HOME}/bin/splunk btool --app=$APP_NAME inputs list script://./bin/$SCRIPT_NAME | grep newPass | sed "s/newPass = //")
+  OLDPASS=$(${SPLUNK_HOME}/bin/splunk btool --app=$APP_NAME inputs list script://./bin/$SCRIPT_NAME | grep oldPass | sed "s/oldPass = //")
+  PRINT_PASS=$(${SPLUNK_HOME}/bin/splunk btool --app=$APP_NAME inputs list script://./bin/$SCRIPT_NAME | grep printPass | sed "s/printPass = //")
+fi
diff --git a/bin/pwchange.bat b/bin/pwchange.bat
deleted file mode 100755
index a725d01..0000000
--- a/bin/pwchange.bat
+++ /dev/null
@@ -1,85 +0,0 @@
-@echo off
-REM Define the original and new passwords here. To use automatic password generation, change NEWPASS to 'auto'
-SET OLDPASS=changeme
-SET NEWPASS=auto
-
-REM Configure if the random password generated should be printed into the output that is sent back to Splunk. Default is "0" which means it's NOT printed. Change to "1" to print.
-SET PRINT_PASS=0
-
-REM Settings for automatic password generation. Not used if NEWPASS is not set to 'auto'
-Setlocal EnableDelayedExpansion
-SET _RNDLength=16
-SET _Alphanumeric=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
-SET _Str=%_Alphanumeric%987654321
-
-REM Other variables relating to the checkpoint file and the path to test login and hostname for logging
-SET CHECKPOINT=%SPLUNK_HOME%\etc\pwd_changed
-SET LOGIN_COMMAND="%SPLUNK_HOME%\bin\splunk.exe" login -auth admin:%OLDPASS%
-FOR /F "usebackq" %%i IN (`hostname`) DO SET HOST=%%i
-
-REM Look for the checkpoint file and decide to error or continue
-IF EXIST "%CHECKPOINT%" (
-	goto NOCHANGE
-) ELSE IF "%NEWPASS%"=="auto" (
-	goto AUTOCHANGE
-) ELSE (
-	goto CHANGE
-)
-
-REM Attempt to login to local Splunk account. If successful, generate a new password and change it.
-:AUTOCHANGE
-FOR /F "tokens=2 usebackq" %%C in (`%LOGIN_COMMAND%`) DO SET LOGIN=%%C  
-:_LenLoop
-IF NOT "%LOGIN%"=="Failed" (
-IF NOT "%_Str:~18%"=="" SET _Str=%_Str:~9%& SET /A _Len+=9& GOTO :_LenLoop
-SET _tmp=%_Str:~9,1%
-SET /A _Len=_Len+_tmp
-Set _count=0
-SET NEWPASS=
-:_loop
-Set /a _count+=1
-SET _RND=%Random%
-Set /A _RND=_RND%%%_Len%
-SET NEWPASS=!NEWPASS!!_Alphanumeric:~%_RND%,1!
-If !_count! lss %_RNDLength% goto _loop
-"%SPLUNK_HOME%\bin\splunk.exe" edit user admin -password "%NEWPASS%" >NUL
-	goto AUTOSUCCESS
-) ELSE (
-	goto FAILED
-)
-
-REM Attempt to login to local Splunk account. If successful, generate a new password and change it.
-:CHANGE
-FOR /F "tokens=2 usebackq" %%C in (`%LOGIN_COMMAND%`) DO SET LOGIN=%%C 
-IF NOT "%LOGIN%"=="Failed" (
-"%SPLUNK_HOME%\bin\splunk.exe" edit user admin -password "%NEWPASS%" >NUL
-	goto SUCCESS
-) ELSE (
-	goto FAILED
-)
-
-REM Create the checkpoint file and log success. This will print the password in the log message passed back to Splunk.
-:AUTOSUCCESS
-echo %date% %time% %HOST%: Splunk account password successfully changed. > "%CHECKPOINT%"
-IF NOT "%PRINT_PASS%"=="0" (
-echo %date% %time% %HOST%: Splunk account password successfully changed. Automatic password: %NEWPASS%
-) ELSE (
-echo "%date% %time% %HOST%: Splunk account password successfully changed. Automatic password: **********"
-)
-exit
-
-REM Create the checkpoint file and log success.
-:SUCCESS
-echo %date% %time% %HOST%: Splunk account password successfully changed. > "%CHECKPOINT%"
-echo %date% %time% %HOST%: Splunk account password successfully changed.
-exit
-
-REM Login failure
-:FAILED
-echo %date% %time% %HOST%: Splunk account login failed. Old password is not correct for this host.
-exit
-
-REM Log that the checkpoint file exists
-:NOCHANGE
-echo %date% %time% %HOST%: Splunk account password was already changed.
-exit
diff --git a/bin/pwchange.ps1 b/bin/pwchange.ps1
new file mode 100755
index 0000000..39e385d
--- /dev/null
+++ b/bin/pwchange.ps1
@@ -0,0 +1,96 @@
+### Grab variables from inputs.conf
+$BTOOL_INPUT = & $SPLUNKHOME\bin\splunk.exe cmd btool inputs list powershell://pw_change
+$SPLUNK_USER_NAME = ($BTOOL_INPUT | findstr splunkUserName).Split(" ")[2]
+$NEW_PASS_SETTING = ($BTOOL_INPUT | findstr newPass).Split(" ")[2]
+$OLD_PASS_SETTING = ($BTOOL_INPUT | findstr oldPass).Split(" ")[2]
+$PRINT_PASS_SETTING = ($BTOOL_INPUT | findstr printPass).Split(" ")[2]
+
+### Configure file paths for the checkpoint
+$PW_CHANGED = "$SPLUNKHOME\etc\pw_changed"
+$PW_CHECK = (Test-Path -Path "$PW_CHANGED")
+
+### Function for generation of a random password
+Function GeneratePassword
+{
+    $MinimumPasswordLength = 12
+    $MaximumPasswordLength = 16
+    $PasswordLength = Get-Random -InputObject ($MinimumPasswordLength..$MaximumPasswordLength)
+    $AllowedPasswordCharacters = [char[]]'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?@#£$%^&'
+    $Regex = "(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\W)"
+
+    do {
+            $Password = ([string]($AllowedPasswordCharacters |
+            Get-Random -Count $PasswordLength) -replace ' ')
+       }    until ($Password -cmatch $Regex)
+
+    $Password
+
+}
+
+### Filter to attach timestamps where necessary
+filter timestamp {"$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff zzz') ${env:COMPUTERNAME}: $_"}
+
+### Check to see if there is a pw_changed file under $SPLUNKHOME\etc and bail out if there is
+if ( $PW_CHECK -eq "True" )
+{
+    Write-output "Splunk account password was already changed." | timestamp
+    Exit
+}
+
+### Capture the current password hash to check for a successful change
+$PASS_HASH_GET = Get-ChildItem $SPLUNKHOME\etc\passwd | select-string $SPLUNK_USER_NAME
+$OLD_HASH = ($PASS_HASH_GET -Split {$_ -eq ":"}) | findstr "\$"
+
+### Set random password if newPass is "auto"
+if ( $NEW_PASS_SETTING -eq "auto" )
+{
+    Write-output "Configuring random password..." | timestamp
+    $NEWPASS = (GeneratePassword)
+    $OLDPASS = $OLD_PASS_SETTING
+    if ( $PRINT_PASS_SETTING -eq "true" -or $PRINT_PASS_SETTING -eq "1" )
+    {
+        Write-output "New password: ${NEWPASS}" | timestamp
+    }
+    & ${SPLUNKHOME}\bin\splunk edit user ${SPLUNK_USER_NAME} -password ${NEWPASS} -auth ${SPLUNK_USER_NAME}:${OLDPASS} 2>$null
+    $NEW_HASH_GET = Get-ChildItem $SPLUNKHOME\etc\passwd | select-string $SPLUNK_USER_NAME
+    $NEW_HASH = ($NEW_HASH_GET -Split {$_ -eq ":"}) | findstr "\$"
+    if ( $NEW_HASH -ne $OLD_HASH ) {
+        Write-output "Password changed." | timestamp
+        Out-File -FilePath "$PW_CHANGED"
+    } else {
+        Write-output "Password change failed. Creating user-seed.conf." | timestamp
+        Rename-Item -Path $SPLUNKHOME\etc\passwd -NewName $SPLUNKHOME\etc\passwd.bak
+        "[user_info]" | Out-File -FilePath $SPLUNKHOME\etc\system\local\user-seed.conf
+        "USERNAME = ${SPLUNK_USER_NAME}" | Out-File -FilePath $SPLUNKHOME\etc\system\local\user-seed.conf -Append
+        "PASSWORD = ${NEWPASS}" | Out-File -FilePath $SPLUNKHOME\etc\system\local\user-seed.conf -Append
+        Out-File -FilePath "$PW_CHANGED"
+    }
+}
+
+### Set configured password if newPass is not "auto"
+if ( $NEW_PASS_SETTING -ne "auto" )
+{
+    Write-output "Configuring specific password..." | timestamp
+    $NEWPASS = $NEW_PASS_SETTING
+    $OLDPASS = $OLD_PASS_SETTING
+    if ( $PRINT_PASS_SETTING -eq "true" -or $PRINT_PASS_SETTING -eq "1" )
+    {
+        Write-output "New password: '$NEWPASS'" | timestamp
+    }
+    & ${SPLUNKHOME}\bin\splunk edit user ${SPLUNK_USER_NAME} -password ${NEWPASS} -auth ${SPLUNK_USER_NAME}:${OLDPASS} | Out-Null
+    $NEW_HASH_GET = Get-ChildItem $SPLUNKHOME\etc\passwd | select-string $SPLUNK_USER_NAME
+    $NEW_HASH = $NEW_HASH_GET -Split {$_ -eq ":"} | findstr "\$"
+
+    if ( $NEW_HASH -ne $OLD_HASH )
+    {
+        Write-output "Password changed." | timestamp
+        Out-File -FilePath "$PW_CHANGED"
+    } else {
+        Write-output "Password change failed. Creating user-seed.conf." | timestamp
+        Rename-Item -Path $SPLUNKHOME\etc\passwd -NewName $SPLUNKHOME\etc\passwd.bak
+        "[user_info]" | Out-File -FilePath "$SPLUNKHOME\etc\system\local\user-seed.conf"
+        "USERNAME = $SPLUNK_USER_NAME" | Out-File -FilePath "$SPLUNKHOME\etc\system\local\user-seed.conf" -Append
+        "PASSWORD = $NEWPASS" | Out-File -FilePath "$SPLUNKHOME\etc\system\local\user-seed.conf" -Append
+        Out-File -FilePath "$PW_CHANGED"
+    }
+}
diff --git a/bin/pwchange.sh b/bin/pwchange.sh
index 7f1e306..f3c71f1 100755
--- a/bin/pwchange.sh
+++ b/bin/pwchange.sh
@@ -1,35 +1,43 @@
 #!/bin/bash
-# Define the original and new passwords here. To have a password automatically generated, set NEWPASS to 'auto'
-OLDPASS=changeme
-NEWPASS=auto
-
-# Configure if the random password generated should be printed into the output that is sent back to Splunk. Default is "0" which means it's NOT printed. Change to "1" to print.
-PRINT_PASS=0
+## Run appContext to capture details from inputs stanza
+. $(dirname $0)/appContext.sh
 
 # Look for the checkpoint file and error out if it exists
-if [ -f $SPLUNK_HOME/etc/pwd_changed ]
-then
-        echo $(date -R) $HOSTNAME: Splunk account password was already changed.
-        exit
+if [ -f $SPLUNK_HOME/etc/pwd_changed ]; then
+  echo $(date -R) $HOSTNAME: Splunk account password was already changed.
+  exit
 fi
 
-if [ "$NEWPASS" = "auto" ]
-then
+# Generate a random password if newPass is set to "auto" in inputs.conf
+if [ "$NEWPASS" = "auto" ]; then
 	NEWPASS=$(head -c 500 /dev/urandom | sha256sum | base64 | head -c 16 ; echo)
-	if [ "$PRINT_PASS" = "0" ]; then
-		NEWPASSAUTO=$(echo "Automatic password: $NEWPASS")
-	else
-		NEWPASSAUTO=$(echo "Automatic password: **************")
-	fi
 fi
+if [ "$PRINT_PASS" = "1" ] || [ "$PRINT_PASS" = "true" ]; then
+  echo "$(date -R) $HOSTNAME: New password: $NEWPASS"
+fi
+
+# Capture current user password hash
+OLD_PASS_HASH=$(cat $SPLUNK_HOME/etc/passwd | grep $SPLUNK_USER | sed "s/:/ /g" | awk '{ print $2 }')
 
-# Change the password
-$SPLUNK_HOME/bin/splunk edit user admin -password $NEWPASS -auth admin:$OLDPASS > /dev/null 2>&1
+# Attempt to change the password with the provided password in inputs.conf
+$SPLUNK_HOME/bin/splunk edit user $SPLUNK_USER -password $NEWPASS -auth $SPLUNK_USER:$OLDPASS > /dev/null 2>&1
 
-# Check splunkd.log for any error messages relating to login during the script and determine whether the change was successful or not
-CHANGED=$(tail -n 10 $SPLUNK_HOME/var/log/splunk/splunkd.log | grep pwchange | grep Login)
-if [ -z "$CHANGED" ]; then
+# Capture the user password hash to see if it changed
+NEW_PASS_HASH=$(cat $SPLUNK_HOME/etc/passwd | grep $SPLUNK_USER | sed "s/:/ /g" | awk '{ print $2 }')
+
+# Compare the old user hash to the new user hash and generate a user-seed.file if they still match
+if [ "$OLD_PASS_HASH" != "$NEW_PASS_HASH" ]; then
 	echo "$(date -R) $HOSTNAME: Splunk account password successfully changed. $NEWPASSAUTO"
+  touch $SPLUNK_HOME/etc/pwd_changed
 else
-	echo $(date -R) $HOSTNAME: Splunk account login failed. Old password is not correct for this host.
+	echo $(date -R) $HOSTNAME: Password change failed. Creating user-seed.conf.
+  mv "$SPLUNK_HOME/etc/passwd" "$SPLUNK_HOME/etc/password.backup."
+  cat <<<EOF "$SPLUNK_HOME/etc/system/local/user-seed.conf"
+  [user-info]
+  USERNAME = $SPLUNK_USER
+  PASSWORD = $NEWPASS
+
+  EOF
+  touch $SPLUNK_HOME/etc/pwd_changed
+  rm $APP_PATH/bin/DeleteMeToRestart
 fi
diff --git a/default/app.conf b/default/app.conf
index 8ff339b..37d1a7f 100644
--- a/default/app.conf
+++ b/default/app.conf
@@ -9,4 +9,4 @@ is_visible = false
 is_manageable = false
 
 [launcher]
-version = 2.0.0
+version = 2.1.0
diff --git a/default/inputs.conf b/default/inputs.conf
index f7e6f67..88fb636 100644
--- a/default/inputs.conf
+++ b/default/inputs.conf
@@ -70,10 +70,19 @@ index = _internal
 sourcetype = pw_change:output
 interval = -1
 source = pw_change_output
+splunkUserName = admin
+newPass = auto
+oldPass = changeme
+printPass = false
 
-[script://.\bin\pwchange.bat]
+[powershell://pwchange]
 disabled = 1
 index = _internal
 sourcetype = pw_change:output
 interval = -1
 source = pw_change_output
+script = . "$SplunkHome\etc\apps\SplunkForwarderRepairKit\bin\pwchange.ps1"
+splunkUserName = admin
+newPass = auto
+oldPass = changeme
+printPass = false
