All pages
Powered by GitBook
1 of 6

Custom Scripts

Rotation of passwords for SaaS accounts or REST API endpoints using custom scripts

Overview

Keeper supports custom scripting for the purpose of rotation or other actions. To demonstrate this capability, a few examples have been published.

The new method of custom rotations is using Keeper's new SaaS Rotation capability.

Example Post-Rotation Scripts

  • Okta User

  • Snowflake User

  • REST API

  • CIsco IOS XE

  • Cisco Meraki

Okta User

Rotating Okta user accounts using the Okta API

Overview

This documentation explains how to rotate Okta accounts using KeeperPAM's rotation option called "Run PAM scripts only". This is a setting in the PAM User rotation settings which tells the Gateway to skip the primary rotation method and directly execute the post-rotation script attached to the PAM User record in the vault.

Prerequisites

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

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

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

  • Okta API Token: You will need an Okta API Token to interact with the Okta API.

Obtain Okta API Token

  1. Follow the steps in the official Okta documentation 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 "Okta 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 Okta User details whose password will be rotated.

  • Set the username to match the Okta user's email address.

  • Set the password to the current password set for the user.

Okta SDK only supports password rotation if the current password is valid. If the password is incorrect, the rotation will fail.

Step 3: Add PAM Script

  • Attach the below Python or Bash script that will perform the password rotation.

  • Add the "Additional Credential" record, which is the "Okta API Access Details" record created in Step 1.

In the example below, we'll use the bash script because the Keeper Gateway is running as a Docker container.

Step 4: Configure Password Rotation Settings

  • Rotation Type: Set it to "Run PAM scripts only"

  • PAM Configuration: Select the configuration for your environment

Environment Setup

In order for the post-rotation script to execute, the host where the Keeper Gateway is running needs to have the necessary execution environment available.

Bash Script

The Bash script below works on the latest Docker version of the Keeper Gateway or any Linux environment that has jq installed.

#!/usr/bin/env bash

# CAUTION: Okta only allows password reset where the previous password is supplied.
# If the operation fails, this script will NOT roll back to the old password.
# Please ensure that the old password is correct before running this script.
# This script is provided as an example only and is not supported by Keeper Security.

# This will be executed as the following
# history -c && echo "BASE64STRING==" | /path/to/script.sh

# Without this the script might report a success
# if something fails in the script.
set -o pipefail -e

IFS= read -r params
json=$(echo "$params" | base64 -d)

# There is no built in JSON parser.
# In order to parse JSON, a tool like jq or fx is required.
$(echo "$json" | jq -r 'keys[] as $k | "export \($k)=\(.[$k])"')

# Set your Okta API token and organization URL
recordJson=$(echo "$records" | base64 -d)
OKTA_API_RECORD="Okta API Access Details"
OKTA_API_TOKEN=$(echo "$recordJson" | jq -r ".[] | select(.title == \"$OKTA_API_RECORD\").password")
OKTA_ORG_URL=$(echo "$recordJson" | jq -r ".[] | select(.title == \"$OKTA_API_RECORD\").url")

# Set the user's ID and the new password
USER_ID="$user"
OLD_PASSWORD="$oldPassword"
NEW_PASSWORD="$newPassword"

curl -v -X POST \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: SSWS "$OKTA_API_TOKEN"" \
-d '{
    "oldPassword": "'"$OLD_PASSWORD"'",
    "newPassword": "'"$NEW_PASSWORD"'"
}' "$OKTA_ORG_URL/api/v1/users/$USER_ID/credentials/change_password"

Python Script

The Python script below is well-commented and follows best practices. It imports necessary modules, initializes variables, and defines functions for various tasks like finding a password by its title, fetching all Okta users, and rotating the password for the particular user.

#!/usr/local/bin/python3

import asyncio
import sys
import base64
import json

# CAUTION: Okta SDK only allows password reset where the previous password is supplied.
# If the operation fails, this script will NOT roll back to the old password.
# Please ensure that the old password is correct before running this script.

# Display Python version
print(f"# Python version: {sys.version}")

# Display installed packages. Uncomment lines below to inspect the environment to make sure that the
# required packages are installed.

# import pkg_resources
# print("# \n# Print installed packages for debugging purposes:")
# 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 Okta modules
try:
    import okta
    from okta.client import Client as OktaClient
except ImportError:
    print("# Error: Okta client package is not installed. Run 'pip install okta' to install.")
    exit(1)


# Function to find a password by its title in the records
def find_password_by_title(records, target_title):
    """Search for a password by its title in the given records."""
    for record in records:
        if record['title'] == target_title:
            return record['password']
    return None  # Return None if no matching record is found


# Initialize variables
okta_api_access_details_title = 'Okta API Access Details'
okta_api_access_details_record = None

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

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

    # Decode and load records
    records = json.loads(base64.b64decode(params.get('records')).decode())

    # Find the Okta API access details record
    okta_api_access_details_record = next(
        (record for record in records if record['title'] == okta_api_access_details_title), None)
    break

# Exit if Okta API credentials are not found
if okta_api_access_details_record is None:
    print(f"# Error: No Okta API Credentials record found with title: {okta_api_access_details_title}")
    exit(1)

# Extract Okta configuration details
okta_org_url = okta_api_access_details_record.get('url')
okta_org_token = okta_api_access_details_record.get('password')
old_password = params.get('oldPassword')
new_password = params.get('newPassword')

# Initialize Okta client
config = {
    "orgUrl": okta_org_url,
    "token": okta_org_token
}
okta_client = OktaClient(config)


async def get_all_users():
    """Fetch all Okta users."""
    users, _, err = await okta_client.list_users()
    if err:
        print(f"# Error: {err}")
    return users


async def get_okta_user_by_email(email):
    """Fetch an Okta user by their email."""
    print(f"# Fetching all Okta users...")
    users = await get_all_users()

    print(f"# Searching for user with email: {email}")
    found_user = next((user for user in users if user.profile.email == email), None)

    return found_user


async def rotate(user_id_to_rotate, old_password, new_password):
    """Rotate the password for a given Okta user."""

    change_pwd_request = {
        "oldPassword": old_password,
        "newPassword": new_password,
    }

    result, _, err = await okta_client.change_password(user_id_to_rotate, change_pwd_request)
    if err:
        print(f"# Error: {err}")
    else:
        print(f"# Password changed successfully for user: {user_id_to_rotate}")


async def main():
    """Main function to execute the password rotation."""
    user_email = params.get('user')

    print(f"# Fetching Okta user by email: {user_email}. This is required to get the user's Okta ID.")
    print(f"# If user id is present then there is no need to fetch the user by email and instead just use the user id.")
    user_from_okta = await get_okta_user_by_email(user_email)

    print(f"# Getting user id from Okta user")
    user_from_okta_id = user_from_okta.id if user_from_okta else None

    print(f"# Rotating password for user: {user_from_okta_id}")
    if user_from_okta_id:
        await rotate(user_from_okta_id, old_password, new_password)
    else:
        print("# Error: User {user_email} not found in Okta.")
        print("# Please ensure that the user exists in Okta and try again.")


if __name__ == '__main__':
    print("# Starting the main async function...")
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Snowflake User

Rotating Snowflake users within your Keeper Vault

Overview

This documentation explains how to rotate Snowflake accounts using KeeperPAM's rotation option called "Run PAM scripts only". This is a setting in the PAM User rotation settings which tells the Gateway to skip the primary rotation method and directly execute the post-rotation script attached to the PAM User record in the vault.

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

Prerequisites

  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. snowflake.connector Library: Ensure that the snowflake connector library is installed in your python environment.

Snowflake connector installation

The Snowflake Connector for Python provides an interface for developing Python applications that can connect to Snowflake and perform all standard operations. You should have snowflake connector library installed in your python environment to successfully run the post-rotation script. To install snowflake connector, activate a Python virtual environment in your keeper-gateway environment and run the following command:

pip install snowflake-connector-python

NOTE: If you want to use a virtual environment, add a shebang line at the top of the script as documented here Python Environment Setup

Rotating Setup for Snowflake User credentials

Follow these steps to successfully setup rotation on Snowflake User Records:

Step 1: Set Up Rotation Record

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

  • Set the username to match the Snowflake user's email address.

  • Set the password to the current password set for the user.

Step 2: Add PAM Script

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

  • 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 3: 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

Post-Rotation Python Script

PAM script to rotate Snowflake user credentials:

#!/usr/local/bin/pam_rotation_venv_python3

'''
Password rotation script for Snowflake user accounts.

This script is designed to rotate the password for a given Snowflake user using the snowflake connector package.
It facilitates the automated updating of user password in your Snowflake environment.

NOTE: If spaces are present in the path to the python interpreter, the script will fail to execute.
    This is a known 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
'''
import asyncio
import json
import sys
import base64

'''
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 snowflake connector package
try:
    import snowflake.connector
except ImportError:
    print("# Error: The 'snowflake connector' package could not be imported. Run 'pip install snowflake-connector-python' to install it.")
    exit(1)

def rotate(snowflake_account_name, snowflake_admin_user, snowflake_admin_pass, snowflake_user_name, new_password):
    """
    Connects with Snowflake using the snowflake.connector module.
    Rotate the password for a given Snowflake user.

    Args:
    - snowflake_account_name (str): The name of the Snowflake account to connect to.
    - snowflake_admin_user (str): The username of the Snowflake admin account.
    - snowflake_admin_pass (str): The password of the Snowflake admin account.
    - snowflake_user_name (str): The name of the Snowflake user whose password needs to be rotated.
    - new_password (str): The new password to be set for the Snowflake user.

    Returns:
    - None
    """

    # Connect with snowflake account using snowflake.connector module
    try:
        conn = snowflake.connector.connect(
        user=snowflake_admin_user,
        password=snowflake_admin_pass,
        account=snowflake_account_name
        )
    except Exception as E:
        print(f"Unable to connect to snowflake account. Error: {E}")
        exit(1)
    
    # Create a cursor object
    cur = conn.cursor()
    
    # Change new user's password
    try:
        change_pass_query = f"ALTER USER {snowflake_user_name} SET PASSWORD = '{new_password}'"
        cur.execute(change_pass_query)
    except Exception as E:
        print(f"Unable to update the password. Error: {E}")
        exit(1)

    # Close the cursor and connection
    cur.close()
    conn.close()

    print(f"Password successfully rotated for the given Snowflake User - {snowflake_user_name}")

def main():
    """
    Main function to rotate the password for a given Snowflake User.

    Reads and decodes input parameters from stdin, including the authentication record details
    and the new password. Then, updates the password of the specified Snowflake user.

    Args:
    - None

    Returns:
    - None
    """
    record_title = 'Snowflake Authentication Record' #This should be same as the title of the record containing admin credentials.
    admin_credential_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}")
        '''

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

        # Find the Record that contains the admin account details by its Title
        admin_credential_record = next((record for record in records if record['title'].lower() == record_title.lower()), None)
        break

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

    # Extract Details from the record
    snowflake_account_name = admin_credential_record.get('snowflake_account_name')
    snowflake_admin_user = admin_credential_record.get('login')
    snowflake_admin_pass = admin_credential_record.get('password')
    
    # Username for the user whose password needs to be rotated.
    snowflake_user_name = params.get('user')

    # Extract new rotated password..
    new_password = params.get('newPassword')
    
    if not all([snowflake_account_name, snowflake_admin_user, snowflake_admin_pass]):
        print("# Error: One or more required fields are missing in the authentication record.")
        exit(1)
   
    # Rotate the password for a given Snowflake user.
    rotate(snowflake_account_name, snowflake_admin_user, snowflake_admin_pass, snowflake_user_name, new_password)

if __name__ == "__main__":
    main()

The above script for the Snowflake Post-Rotation Script can be also found here:

https://github.com/Keeper-Security/Zero-Trust-KeeperPAM-Scripts/tree/main/snowflakegithub.com

Rotating Snowflake User Credentials

After successfully setting up Rotation for your Snowflake User Credentials on the PAM User Record, clicking on "Run Scripts Only" will rotate the credential:

Rotate Credential via REST API

Automatically rotate 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 KeeperPAM's rotation option called "Run PAM scripts only". This is a setting in the PAM User rotation settings which tells the Gateway to skip the primary rotation method and directly execute the post-rotation script attached to the PAM User record in the vault.

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}")

Cisco IOS XE

Rotate your Cisco IOS XE Network Credentials

Overview

In this guide, you will learn how to set up password rotation to rotate Cisco IOS XE network credentials.

Prerequisites

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

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

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

  • Requests Library: Ensure that the requests library is installed in your Python environment. This library is necessary for making HTTP requests to Cisco devices.

  • Setting up AnyConnect Cisco VPN: In order to connect to cisco devices, ensure that the machine hosting Keeper Gateway has Cisco AnyConnect VPN installed and properly configured

  • Test Cisco Device Connectivity

Requests library installation

The Requests library allows you to send HTTP requests easily. Activate a Python virtual environment in your Keeper Gateway environment and install the library using the following command:

pip install requests

Setting up AnyConnect Cisco VPN

Ensure that the machine hosting Keeper Gateway has Cisco AnyConnect VPN installed and properly configured in order to connect to cisco device. This setup is necessary for establishing secure connections to Cisco devices.

Steps to Test Cisco Device

Following these steps will allow you to test the Cisco device and create a new user in the Cisco sandbox environment.

Note: If you want to use a virtual environment, add a shebang line at the top of the script as documented here in the Python Environment Setup.

1. Login to Cisco Sandbox

  • Go to the Cisco DevNet Sandbox

  • Log in with your Cisco account credentials.

  • Select and launch the sandbox.

2. Select and Launch the Device

  • Navigate to the sandbox catalog.

  • Select the appropriate sandbox for your Cisco device (e.g., Cisco IOS XE, etc.).

  • Launch the sandbox.

3. Receive Details via Email or DevNet Environment

After launching the sandbox, you will receive an email with the connection details or find them in the DevNet Environment under Quick Access.

4. Download Cisco AnyConnect VPN

  • Download and install the Cisco AnyConnect Secure Mobility Client.

  • Get detailed connection instructions here.

5. Connect to the VPN

  • Open the Cisco AnyConnect Secure Mobility Client.

  • Enter the VPN connection details provided in the email or from the DevNet Environment.

  • Connect using the provided username and password.

6. Store Developer Credentials

At this point, you will see Developer Credentials—a host, username, and password. Store these values in a Keeper Security record of type Login named as Cisco Authentication Record. You will need this Keeper Security record name in order to run the post-rotation script.

7. Add Custom Field to Cisco Authentication Record

Add a custom field named host_endpoint to the Cisco Authentication Record and set its value to the host address (e.g., 10.10.20.48).

8. Create a User

  • Open your terminal or SSH client.

  • Connect to the Cisco device using the provided IP address and credentials.

9. Follow These Steps to Create a User

  1. Login with Admin User (developer):

    ssh developer@<device-ip>
  2. Enable privileged commands:

    enable
  3. Enter configuration mode:

    configure terminal
  4. Create a new user with a password:

    username <user> password <pass>

10. Test the New User

Login with the new user:

ssh <user>@<device-ip>

Note: Replace <user> with the username you created and <device-ip> with the IP address of the Cisco device.

Setting up Rotation in your Vault

Once you have your prerequisites ready, make sure you cover the following:

  1. Make sure you satisfy all the prerequisites

  2. Ensure that the post-rotation script references the Keeper Security record containing your Cisco admin credentials.

  3. Attach the post-rotation script to a Keeper Security PAM user record using the Keeper Security documentation. When this record has its secrets rotated, the post-rotation script will execute and update the password for the specified Cisco device user.

Step 1: Set Up Rotation Record

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

  • Set the username to match the Cisco device admin credentials

  • Set the password to the current password set for the user.

  • Add a custom field named host_endpoint to the Cisco Authentication Record and set its value to the host address (e.g., 10.10.20.48).

Step 2: Add PAM Script

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

Step 3: Add NOOP Custom Field

  • 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

Python Script

PAM script to rotate Cisco IOS XE user credentials:

'''
Password rotation script for Cisco user accounts.

This script is designed to rotate the password for a given Cisco user.
It facilitates the automated updating of user password in your Cisco environment.

NOTE: If spaces are present in the path to the python interpreter, the script will fail to execute.
    This is a known 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
'''

import sys
import base64
import json
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
'''
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 package
try:
    import requests
except ImportError:
    print("# Error: The 'requests' package is not installed. Run 'pip install requests' to install it.")
    exit(1)

def get_username_details(cisco_url, cisco_admin_username, cisco_admin_password, cisco_user_name):
    """
    Verify the Cisco user.
    Args:
    - cisco_url (str): The host endpoint of the Cisco account to connect to.
    - cisco_admin_username (str): The username of the Cisco admin account.
    - cisco_admin_password (str): The password of the Cisco admin account.
    - cisco_user_name (str): The name of the Cisco user whose password needs to be rotated.
    Returns:
    - True if username found.
    """
    
    # Constructs the request URL for the username API endpoint
    request_url = f"{cisco_url}username/"
    
    # Sets the headers for the RESTCONF request, specifying that we expect and send YANG data in JSON format
    headers = {
    'Accept': 'application/yang-data+json',
    'Content-Type': 'application/yang-data+json'
    }
    try:
        # Sends a GET request to the Cisco router to fetch user details
        response = requests.get(request_url, headers=headers, auth=(cisco_admin_username,cisco_admin_password), verify=False)
        response.raise_for_status()
        data = response.json()
        # Extracts the list of usernames from the response data
        usernames = data["Cisco-IOS-XE-native:username"]
        # Iterates through the list of usernames to find the specified user
        for user in usernames:
            if user["name"]==cisco_user_name:
                # Returns True if the specified username is found
                return True
    except requests.exceptions.HTTPError as http_err:
        print(f"HTTP error occurred while fetching username details from Cisco router: {http_err}")
    except Exception as err:
        print(f"An error occurred: {err}")
    return False

def rotate(cisco_url, cisco_admin_username, cisco_admin_password, cisco_user_name, new_password):
    """
    Rotate the password for a given Cisco user.
    Args:
    - cisco_url (str): The host endpoint of the Cisco account to connect to.
    - cisco_admin_username (str): The username of the Cisco admin account.
    - cisco_admin_password (str): The password of the Cisco admin account.
    - cisco_user_name (str): The name of the Cisco user whose password needs to be rotated.
    - new_password (str): The new password to be set for the Cisco user.
    Returns:
    - None
    """
    
    # Calls the function get_username_details to check if the specified user exists on the Cisco router
    user = get_username_details(cisco_url, cisco_admin_username, cisco_admin_password, cisco_user_name)

    # If the user does not exist, print an error message and exit the program
    if not user:
        print(f"No user found with the username: {cisco_user_name}")
        exit(1)
    
    # Sets the headers for the RESTCONF request, specifying that we expect and send YANG data in JSON format
    headers = {
    'Accept': 'application/yang-data+json',
    'Content-Type': 'application/yang-data+json'
    }

    # Creates the data payload for the PATCH request to update the user's password
    data = {
    "Cisco-IOS-XE-native:native": {
        "username": [
                {
                "name": cisco_user_name,
                "password": {
                    "password": new_password
                    }
                }
            ]
        }
    }
    
    try:
        # Sends a PATCH request to the Cisco router to update the user's password
        response = requests.patch(cisco_url, headers=headers, auth=(cisco_admin_username,cisco_admin_password), data=json.dumps(data), verify=False)
        response.raise_for_status()
        print(f"Password updated successfully for user {cisco_user_name}")
    except requests.exceptions.HTTPError as http_err:
        print(f"HTTP error occurred while updating the password for the given user: {http_err}")
    except Exception as err:
        print(f"An error occurred: {err}")

def main():
    """
    Main function to rotate the password for a Cisco device user.

    Reads and decodes input parameters from stdin, including the authentication record details
    and the new password. Then, updates the password of the specified Cisco device user.
    """
    record_title = 'Cisco Authentication Record' #This should be same as the title of the record containing username, password and host endpoint details. 
    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())

        # Decode and load records passed in as JSON strings from the PAM Script section as "Rotation Credential" records
        records = json.loads(base64.b64decode(params.get('records')).decode())
        # Find the record that matches the specified title
        api_access_token_record = next((record for record in records if record['title'].lower() == record_title.lower()), None)
        break

    if api_access_token_record is None:
        print(f"# Error: No Record with the access token found. Title: {record_title}")
        exit(1)
    
    # Extract Details from the record
    # HostName endpoint of the Cisco device endpoint.
    cisco_router_endpoint = api_access_token_record.get('host_endpoint') 
    # Admin username for the Cisco device
    cisco_admin_username = api_access_token_record.get('login')
    # Admin password for the Cisco device
    cisco_admin_password = api_access_token_record.get('password')

    # Username of the Cisco device user whose password needs to be rotated
    cisco_user_name = params.get('user')
    # New password to set for the Cisco device user
    new_password = params.get('newPassword')
    
    # Check if all required fields are present
    if not all([cisco_router_endpoint, cisco_admin_username, cisco_admin_password, cisco_user_name]):
        print("# Error: One or more required fields are missing in the access token record.")
        exit(1)
    
    # Construct the Cisco API URL
    cisco_url = f"https://{cisco_router_endpoint}/restconf/data/Cisco-IOS-XE-native:native/"

    # Rotate the password for the specified Cisco device user
    rotate(cisco_url, cisco_admin_username, cisco_admin_password, cisco_user_name, new_password)

if __name__ == "__main__":
    main()

The above script for the Cisco Post-Rotation Script can be also found here:

https://github.com/Keeper-Security/Zero-Trust-KeeperPAM-Scripts/blob/main/cisco-ios-xe/update-cisco-user.pygithub.com

Rotating Cisco IOS XE Network User Credentials

Note: The user whose password is getting rotated should not be an administrator and must be Authorized for Client VPN [While adding the user via user management portal, the authorized option should be selected as 'Yes'].

After successfully setting up Rotation for your Cisco User Credentials on the PAM User Record, clicking on "Run Scripts Only" will rotate the credential:

Cisco Meraki

Rotate your Cisco Meraki Network Credentials

Overview

In this guide, you will learn how to set up password rotation to rotate Cisco Meraki network credentials.

Prerequisites

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

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

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

  • Requests Library: Ensure that the requests library is installed in your Python environment. This library is necessary for making HTTP requests to Cisco devices.

  • Setting up AnyConnect Cisco VPN: In order to connect to cisco devices, ensure that the machine hosting Keeper Gateway has Cisco AnyConnect VPN installed and properly configured

  • Test Cisco Device Connectivity

Requests library installation

The Requests library allows you to send HTTP requests easily. Activate a Python virtual environment in your Keeper Gateway environment and install the library using the following command:

pip install requests

Setting up AnyConnect Cisco VPN

Ensure that the machine hosting Keeper Gateway has Cisco AnyConnect VPN installed and properly configured inorder to connect to cisco device. This setup is necessary for establishing secure connections to Cisco devices.

Steps to Test Cisco Device

Following these steps will allow you to test the Cisco device and create a new user in the Cisco sandbox environment.

Note: If you want to use a virtual environment, add a shebang line at the top of the script as documented here in the Python Environment Setup.

Login to Cisco Sandbox

  • Go to the Cisco DevNet Sandbox

  • Log in with your Cisco account credentials.

  • Select and launch the sandbox.

2. Select and Launch the Device

  • Navigate to the sandbox catalog.

  • Select the appropriate sandbox for your Cisco device (e.g., Cisco IOS XE, etc.).

  • Launch the sandbox.

3. Receive Details via Email or DevNet Environment

After launching the sandbox, you will receive an email with the connection details or find them in the DevNet Environment under Quick Access.

4. Download Cisco AnyConnect VPN

  • Download and install the Cisco AnyConnect Secure Mobility Client.

  • Get detailed connection instructions here.

5. Connect to the VPN

  • Open the Cisco AnyConnect Secure Mobility Client.

  • Enter the VPN connection details provided in the email or from the DevNet Environment.

  • Connect using the provided username and password.

6. Store Developer Credentials

At this point, you will see Developer Credentials—a host, username, and password. Store these values in a Keeper Security record of type Login named as Cisco Authentication Record. You will need this Keeper Security record name in order to run the post-rotation script.

7. Add Custom Field to Cisco Authentication Record

Add a custom field named network_id to the Cisco Authentication Record and set its value to the host address (e.g., 13.0.0.1).

8. Create a User

  • Open your terminal or SSH client.

  • Connect to the Cisco device using the provided IP address and credentials.

9. Follow These Steps to Create a User

  1. Login with Admin User (developer):

    ssh developer@<device-ip>
  2. Enable privileged commands:

    enable
  3. Enter configuration mode:

    configure terminal
  4. Create a new user with a password:

    username <user> password <pass>

10. Test the New User

Login with the new user:

ssh <user>@<device-ip>

Note: Replace <user> with the username you created and <device-ip> with the IP address of the Cisco device.

Setting up Rotation in your Vault

Once you have your prerequisites ready, make sure you cover the following:

  1. Make sure you satisfy all the prerequisites

  2. Ensure that the post-rotation script references the Keeper Security record containing your Cisco admin credentials.

  3. Attach the post-rotation script to a Keeper Security PAM user record using the Keeper Security documentation. When this record has its secrets rotated, the post-rotation script will execute and update the password for the specified Cisco device user.

Step 1: Set Up Rotation Record

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

  • Set the username to match the Cisco device admin credentials

  • Set the password to the current password set for the user.

  • Add a custom field named network_id to the Cisco Authentication Record and set its value to the host address (e.g., 13.0.0.1).

Step 2: Add PAM Script

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

Step 3: Add NOOP Custom Field

  • 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

Python Script

PAM script to rotate Cisco Meraki user credentials:

'''
Password rotation script for Cisco meraki user accounts.

This script is designed to rotate the password for a given Cisco Meraki User.
It facilitates the automated updating of user password in your Cisco meraki environment.

NOTE: If spaces are present in the path to the python interpreter, the script will fail to execute.
    This is a known 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
'''

import sys
import base64
import json
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
'''
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 package
try:
    import requests
except ImportError:
    print("# Error: The 'requests' package is not installed. Run 'pip install requests' to install it.")
    exit(1)

def fetch_meraki_user_by_email(api_key, network_id, email):
    """
    Fetches User details by email.
    
    Args:
    - api_key (str): The Meraki API key.
    - network_id (str): The network ID to search within.
    - email (str): The email of the user to fetch.
    
    Returns:
    - User details if found, otherwise None.
    """
    if not network_id:
        print("Invalid network ID.")
        return None

    # URL to fetch Meraki dashboard users
    users_url = f"https://api.meraki.com/api/v1/networks/{network_id}/merakiAuthUsers"
    headers = {
        'X-Cisco-Meraki-API-Key': api_key,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }

    try:
        # Make GET request to fetch users
        response = requests.get(users_url, headers=headers)
        response.raise_for_status()
        # Parse response JSON
        users = response.json()

        if users:
            for user in users:
                if user['email'] == email:
                    print("\nUser found for the email-", email)
                    return user
        return None

    except requests.exceptions.RequestException as e:
        print(f"Error fetching Meraki dashboard users: {e}")
        return None

def update_meraki_user_password(api_key, network_id, user_id, new_password):
    """
    Updates the password for a Meraki dashboard user.
    
    Args:
    - api_key (str): The Meraki API key.
    - network_id (str): The network ID the user belongs to.
    - user_id (str): The ID of the user to update.
    - new_password (str): The new password to set.
    
    Returns:
    - bool: True if successful, otherwise False.
    """
    if not network_id:
        print("Invalid network ID.")
        return False

    # URL to update a specific user's password
    user_url = f"https://api.meraki.com/api/v1/networks/{network_id}/merakiAuthUsers/{user_id}"
    headers = {
        'X-Cisco-Meraki-API-Key': api_key,
        'Content-Type': 'application/json',
        'Accept': 'application/json'
    }

    payload = {'password': new_password}

    # Make PUT request to update user's password
    response = requests.put(user_url, headers=headers, json=payload)
    
    return response

def rotate(meraki_network_id, meraki_api_key, meraki_user_email, new_password):
    """
    Rotate the password for a given Cisco user.
    Args:
    - meraki_network_id (str): Network ID of the network where the user is located.
    - meraki_api_key (str): API access key for authorization.
    - meraki_user_email (str): Email of the user whose password needs to be rotated.
    - new_password (str): The new password to be set for the Cisco user.
    Returns:
    - None
    """
    
    # Calls the function fetch_meraki_user_by_email to fetch the user details using user email.
    user = fetch_meraki_user_by_email(meraki_api_key, meraki_network_id, meraki_user_email)

    # If the user does not exist, print the message and exit the program
    if not user:
        print(f"No user found with the email: {meraki_user_email}")
        exit(1)
    
    try:
        meraki_user_id = user['id']

        # Updating password for the given user using ID
        response = update_meraki_user_password(meraki_api_key, meraki_network_id, meraki_user_id, new_password)
        if response.status_code == 200:
            print(f"Password updated successfully for user with email {meraki_user_email}")
        else:
            print(f"Failed to update password. Status code: {response.status_code}, Error: {response.text}")

    except requests.exceptions.HTTPError as http_err:
        print(f"HTTP error occurred while updating the password for the given user email: {http_err}")
    except Exception as err:
        print(f"An error occurred: {err}")

def main():
    """
    Main function to rotate the password for a Cisco meraki user.

    Reads and decodes input parameters from stdin, including the authentication record details
    and the new password. Then, updates the password of the specified Cisco meraki user.
    """
    record_title = 'Cisco Authentication Record' #This should be same as the title of the record containing meraki api key and network ID details. 
    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())

        # Decode and load records passed in as JSON strings from the PAM Script section as "Rotation Credential" records
        records = json.loads(base64.b64decode(params.get('records')).decode())
        # Find the record that matches the specified title
        api_access_token_record = next((record for record in records if record['title'].lower() == record_title.lower()), None)
        break

    if api_access_token_record is None:
        print(f"# Error: No Record with the access token found. Title: {record_title}")
        exit(1)
    
    # Extract Details from the record
    
    # Network ID of the network where the user is located
    meraki_network_id = api_access_token_record.get('network_id')
    
    # API Key for Cisco meraki api authentication
    meraki_api_key = api_access_token_record.get('password')

    # Email of the Cisco meraki user whose password needs to be rotated
    meraki_user_email = params.get('user')

    # New password to set for the Cisco meraki user
    new_password = params.get('newPassword')
    
    # Check if all required fields are present
    if not all([meraki_network_id, meraki_api_key, meraki_user_email]):
        print("# Error: One or more required fields are missing in the access token record.")
        exit(1)

    # Rotate the password for the specified Cisco meraki user
    rotate(meraki_network_id, meraki_api_key, meraki_user_email, new_password)

if __name__ == "__main__":
    main()

The above script for the Cisco Post-Rotation Script can be also found here:

https://github.com/Keeper-Security/Zero-Trust-KeeperPAM-Scripts/blob/main/cisco-meraki/update_meraki_user.pygithub.com

Rotating Cisco Meraki Network User Credentials

Note: The user whose password is getting rotated should not be an administrator and must be Authorized for Client VPN [While adding the user via user management portal, the authorized option should be selected as 'Yes'].

After successfully setting up Rotation for your Cisco User Credentials on the PAM User Record, clicking on "Run Scripts Only" will rotate the credential: