Rotate Credential via REST API

Automatically any cloud-based account using a REST API with Keeper Secrets Manager

Overview

This documentation provides generic instructions on how to set up password rotation using any cloud-based application or API endpoint and PAM Gateway using "NOOP mode". This is a flag set in the Keeper record which tells the Gateway to skip the primary rotation method and directly execute your custom Post-Rotation script.

This guide includes pre-requisites, step-by-step instructions, and a Python script example.

Pre-requisites

  1. KSM Application: Ensure that the Keeper Secrets Manager (KSM) application is set up.

  2. Shared Folder: A shared folder should be set up where all the records will be stored.

  3. PAM Configuration: Ensure that the PAM Configuration is set up and that the Gateway is running and attached to this configuration.

  4. REST API Token: You will need an API Token to interact with the arbitrary API.

Step 1: Obtain REST API Token or Access Token

  1. Follow the steps in your target application or service to generate an API token.

  2. Store this API token in a Keeper record. The record can be of any type, but for this example, we will use a "Login" type.

    • Store the API Token in the "password" field.

    • Store the Organization URL in the "Website Address" field.

  3. Name this record "API Access Details" as this title will be used to fetch the record in the script later.

Step 2: Set Up Rotation Record

Create a new PAM User record to store target User details whose password will be rotated.

  • Set the username to match the user's login ID.

  • Set the password to the current password set for the user (this really depends on the REST endpoint)

Step 3: Add PAM Script

  1. Attach the below Python script that will perform the password rotation. The script has additional comments inside that describe each line.

  2. Add the "Rotation Credential" record, which is the record created in Step 1 containing the API Token and URL.

  3. Enable No-Operation (NOOP) atomic execution:

    • In the current PAM User record where user's details are stored, create a new custom text field labeled NOOP and set its value to True.

Step 4: Configure Password Rotation Settings

  1. Rotation Type: Set it to "On-Demand" for this example.

  2. Password Complexity: Leave it as default unless you have specific requirements.

  3. Rotation Settings: Point to the PAM Configuration set up earlier.

  4. Administrative Credentials Record: Can should be left empty

Step 5: Python Environment Setup

Below steps are related to the environment where the Keeper Gateway is running.

  1. Ensure that the Python environment has all necessary dependencies installed.

  2. If you want to use a virtual environment, add a shebang line at the top of the script.

    • Note: Ensure that the shebang line does not contain spaces. If it does, create a symbolic link without spaces. Example to create a symbolic link on Linux: sudo ln -s "/Users/john/PAM Rotation Example/.venv/bin/python3" /usr/local/bin/pam_rotation_venv_python3

Python Script

The Python script is well-commented and follows best practices. It imports necessary modules, initializes variables, and defines a rotation function to call an arbitrary REST API that changes user's password.

#!/usr/local/bin/pam_rotation_venv_python3

# NOTE: If spaces are present in the path to the python interpreter, the script will fail to execute.
#       This is a knows limitation of the shebang line in Linux and you will need to create a symlink
#       to the python interpreter in a path that does not contain spaces.
#       For example: sudo ln -s "/usr/local/bin/my python3.7" /usr/local/bin/pam_rotation_venv_python3

# NOTE: This script is a demonstration of how to use the REST API to rotate passwords. It does not
#       actually call a real API. You will need to modify the script to call your own API.

import asyncio
import sys
import base64
import json

# Display the Python version for debugging purposes
print(f"# Python version: {sys.version}")

# Optionally display installed packages for debugging. Uncomment if needed.
# import pkg_resources
# print("# \n# Installed packages for debugging:")
# installed_packages = pkg_resources.working_set
# installed_packages_list = sorted(["%s==%s" % (i.key, i.version) for i in installed_packages])
# for m in installed_packages_list:
#     print(f"  {m}")

# Import the requests library
try:
    import requests
except ImportError:
    print("# Error: The 'requests' package is not installed. Run 'pip install requests' to install it.")
    exit(1)

# Initialize variables
api_access_token_title = 'Record with Access Token'
api_access_token_record = None
params = None

# Read and decode input parameters from stdin
for base64_params in sys.stdin:
    params = json.loads(base64.b64decode(base64_params).decode())

    # Optionally print available params for debugging. Uncomment if needed.
    # print(f"# \n# Available params for the script:")
    # for key, value in params.items():
    #     print(f"#     {key}={value}")

    # Decode and load records that are passed into the record as JSON strings 
    # in the PAM Script section as "Rotation Credential" records
    records = json.loads(base64.b64decode(params.get('records')).decode())

    # Find the Record that contains the access token by its Title
    api_access_token_record = next(
        (record for record in records if record['title'] == api_access_token_title), None)
    break

if api_access_token_record is None:
    print(f"# Error: No Record with the access token found. Title: {api_access_token_title}")
    exit(1)

# Extract Details from the record details
rest_api_service_url = api_access_token_record.get('url')
service_access_token = api_access_token_record.get('password')

# Extract details from the record
rest_api_service_url = api_access_token_record.get('url')
service_access_token = api_access_token_record.get('password')
# old_password = params.get('oldPassword')  # in case if you want to use previous password
new_password = params.get('newPassword')
user_email = params.get('user')             # user of the account for which password should be rotated

def rotate(user_email, new_password):
    """
    Rotate the password for a given user.

    Args:
    - user_email (str): The email of the user for whom the password should be rotated.
    - new_password (str): The new password.

    Returns:
    - dict: The response from the API call.
    """

    # Headers for the request
    headers = {
        "Content-Type": "application/json",
        "Authorization": f"Bearer {service_access_token}"
    }

    # Prepare the request payload
    request_payload = {
        "userEmail": user_email,
        "newPassword": new_password,
    }

    # Make a POST request to the API
    response = requests.post(url=rest_api_service_url, headers=headers, data=json.dumps(request_payload))

    # Check if the request was successful
    if response.status_code == 201:
        return response.json()
    else:
        response.raise_for_status()

if __name__ == '__main__':
    """Main function to execute the password rotation."""

    response = rotate(user_email, new_password)

    # Print the response for debugging purposes
    print(f"# \n# Response from the API:")
    print(f"# {response}")

Last updated