Perform privileged automation tasks with Post-Rotation scripts and password rotation
Post-rotation scripts (PAM Scripts) are user-defined software programs that can perform privilege automation tasks. Scripts can be attached to any PAM resource records in the vault. Depending on the PAM record the script is attached to, the script will execute either on the Keeper Gateway, or the remote host where password rotation occurred.
The following table shows all the available PAM Records and where the attached script will execute:
PAM Configuration
Gateway
PAM Machine
The Machine specified in the record
PAM Database
Gateway
PAM Directory
Gateway
PAM User
Gateway
When setting up rotation on a record on a PAM User record, you can select from one of the following methods:
General
IAM User
Run PAM scripts only
When the "General" or "IAM User" methods are selected, Keeper will attempt to rotate the credentials using built-in capabilities based on the information stored in the record.
When the "Run PAM scripts only" option is selected, Keeper will skip the default rotation task and immediately run the attached PAM scripts on the gateway.
Scripts will be executed in the following order:
Scripts attached on PAM User records
Scripts attached on PAM Machine, PAM Database, or PAM Directory Record types
Scripts attached on PAM Configuration Record types
If multiple scripts are attached to a record, scripts will be executed in the order they appear on the PAM Record.
Here are some of the use cases made possible with Keeper Post-Rotation Scripts:
Custom rotation scripts for any type of target
Revoking access to a resource
Sending notifications to team members
Propagating the password change to other systems
Any other custom privilege automation task
Description of the input parameters passed into PAM Scripts
Upon successful rotation of credentials on a PAM record, Keeper executes the attached Post-Rotation scripts with parameters containing information on the involved records, credentials, and user.
The Keeper Gateway executes PAM scripts and provides inputs to the script through stdin parameters. These parameters are placed in a Base64 encoded JSON object and piped to the script.
For example, the Keeper Gateway will essentially execute the script on a Linux machine as follows:
history -c && echo "BASE64==......" | /path/to/script.sh
Windows:
"BASE64==......" | .\script.ps1; Clear-History
The following keys can be found in this base64 encoded JSON object:
providerRecordUid
The UID of the PAM Configuration record
resourceRecordUid
The UID of the PAM Resource record
userRecordUid
The UID of the PAM User record
newPassword
The new password generated for the User
oldPassword
The previous password for the User
user
The username for the User
records
Base64-encoded JSON array of record dictionaries
records
fieldThe records key value is a Base64, JSON array of dictionaries. This array will include the following data:
PAM Configuration information
Related PAM Machine, PAM Database, or PAM Directory Record Data
Additional Records supplied when uploading the post-rotation scripts
User Record Data
Each dictionary object will contain:
uid
- The UID of the Vault record.
title
- The title of the Vault record.
The rest of the dictionary will contain key/value pairs of the record's data where the key will be the label of the field. If the field does not contain a label, the field type will be used. If the key already exists, a number will be added to the key.
Upon execution of the PAM Script, an array is returned containing instances of RotationResult
for each script that was executed. The class RotationResult
has the following attributes:
uid
- Keeper Vault record UID that has the script attached
command
- Command that was issued to the shell
system
- Operating system the script will run upon
title
- Title of the script attached to the Keeper Vault record
name
- Name of the script attached to the Keeper Vault record
success
- Was the script successful?
Linux and macOS - Script returned in a 0 return code.
Windows - Script returned a True status.
stdout
- The stdout from the execution of the script
stderr
- The stderr from the execution of the script
Additionally, the following methods can be used to determine if the script was a success, or not:
was_failure
boolean, return True if failure, False if success
was_success
boolean, returns True if success, False if failure
With this, it is possible to customize logging:
for r in results:
if r.was_failure:
print(f"For record {r.uid}, the script {r.title} failed: {r.stderr}")
The class RotationResult
has attribute stderr
which logs the errors from execution of the script.
Although post rotation script results and information are available via the RotationResult
class, errors and outputs of scripts are based on the type of shell the script is executed on. Keeper does not check the stdout or errors of the scripts as Keeper does not know what defines as an error for a customer-controlled script.
For example, if a BASH script does not contain a set -e
, the script will continue even if part of the script fails. If the script exits with a 0
return code, the script will be flagged as successful.
Therefore, it is up to the customer to properly handle the outputs and errors of the script.
Attaching post-rotation scripts to PAM resource records
When creating or editing a PAM record, click on the Add PAM Script button
Clicking on Add PAM Script will allow you to:
Browse locally and choose your Rotation Script(s).
Add "Additional Credentials". This is an option to add additional records which contains the credentials needed by the post rotation script. These credentials must be available to the Keeper Gateway.
Specify an optional custom command to executed. In the below screenshot, a python script (postRotationTest.py
) is attached, and a specific command to be used to execute the python script.
After successfully selecting the script(s), the record will be updated to show the attached Post Rotation scripts:
Click Save to create or update the record. Attached Post Rotation Scripts can be deleted or edited by clicking on their respective actions.
Examples of post-rotation scripts in KeeperPAM
The below example post-rotation scripts simply echo the input parameters in various languages and platforms. The output of the print statements can be found in the Keeper Gateway log file.
Note: For this example, jq needs to be installed to parse the JSON. Attach this as a PAM script and perform the rotation. The Gateway logfile will contain the output.
#!/bin/bash
# Read the Base64 encoded JSON input and decode it
decoded_json=$(cat | base64 --decode)
# Extract the "records" field, which is Base64 encoded, and decode it separately
records_base64=$(echo "$decoded_json" | jq -r '.records')
# Decode the Base64 "records" field and pretty-print the JSON
decoded_records=$(echo "$records_base64" | base64 --decode | jq '.')
# Print the entire decoded JSON, replacing "records" with the decoded version
echo "$decoded_json" | jq --argjson records "$decoded_records" '.records = $records'
Attach this as a PAM script and perform the rotation. The Keeper Gateway logfile will contain the output. This script simply echoes the input.
Begin {
# Executes once before first item in pipeline is processed
}
Process {
# Stop if error. If not set, result value will be True and assumed there
# was no problem.
$ErrorActionPreference = "Stop"
# Executes once for each pipeline object
$JSON = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($_))
$Params = ($JSON | ConvertFrom-Json)
Write-Output "providerRecordUid=$($Params.providerRecordUid)"
Write-Output "resourceRecordUid=$($Params.resourceRecordUid)"
Write-Output "userRecordUid=$($Params.userRecordUid)"
Write-Output "newPassword=$($Params.newPassword)"
Write-Output "oldPassword=$($Params.oldPassword)"
Write-Output "user=$($Params.user)"
$recordsJSON = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Params.records))
$records = ($recordsJSON | ConvertFrom-Json)
# Output full JSON for records
Write-Output "Full Records JSON: $recordsJSON"
# Extract the provider title from the records
$title = ($records | Where-Object {$_.uid -eq $Params.providerRecordUid}).title
Write-Output "Provider Title=$title"
# Loop through all records and display details
foreach ($record in $records) {
Write-Output "Record UID=$($record.uid)"
Write-Output "Record Title=$($record.title)"
Write-Output "Record Type=$($record.type)"
Write-Output "Record Details=$($record.details | ConvertTo-Json)"
}
}
End {
# Executes once after last pipeline object is processed
}
Here's a PowerShell script that sends a Webhook to a 3rd party site.
param (
[Parameter(ValueFromPipeline=$true)]
[string]
$Record
)
# Decode the Base64 input and convert it to a PowerShell object
$RecordJsonAsB64 = [System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Record))
$Params = $RecordJsonAsB64 | ConvertFrom-Json
# Prepare the webhook payload
$webhookPayload = @{
providerRecordUid=$Params.providerRecordUid
resourceRecordUid=$Params.resourceRecordUid
userRecordUid=$Params.userRecordUid
user=$Params.user
timestamp= (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssZ")
message= "Post-rotation script executed successfully."
} | ConvertTo-Json
# Define the webhook URL
$webhookUrl = "https://webhook.site/3308ec5a-3fba-4e31-85ad-37b0f643ac82"
# Send the POST request to the webhook
try {
Invoke-RestMethod -Uri $webhookUrl -Method Post -Body $webhookPayload -ContentType 'application/json'
Write-Host "Webhook message sent successfully."
}
catch {
Write-Error "Failed to send webhook message: $_"
}
The post rotation script is not limited to shell scripts. Applications can be written in languages like Python or C# to get the piped parameters. Since the UIDs of the Rotation involved records are passed in the params, the post-rotation script can use the Keeper Secrets Manager SDKs to get additional information.
#!/usr/bin/env python3
import sys
import base64
import json
from keeper_secrets_manager_core import SecretsManager
# sys.stdin is not an array, it can not subscripted (ie sys.stdin[0])
for base64_params in sys.stdin:
params = json.loads(base64.b64decode(base64_params).decode())
print(f"providerRecordUid={params.get('providerRecordUid')}")
print(f"resourceRecordUid={params.get('resourceRecordUid')}")
print(f"userRecordUid={params.get('userRecordUid')}")
print(f"newPassword={params.get('newPassword')}")
print(f"oldPassword={params.get('oldPassword')}")
print(f"user={params.get('user')}")
records = json.loads(base64.b64decode(params.get('records')).decode())
print("Provider Title="
f"{next((x for x in records if x['uid'] == params.get('providerRecordUid')), None).get('title')}")
ksm = SecretsManager(config=...)
resource_records = ksm.get_secrets(params.get('userRecordUid'))[0]
break