LogoLogo
KeeperPAM and Secrets Manager
KeeperPAM and Secrets Manager
  • KeeperPAM
  • Privileged Access Manager
    • Setup Steps
    • Quick Start: Sandbox
    • Getting Started
      • Architecture
        • Architecture Diagram
        • Vault Security
        • Router Security
        • Gateway Security
        • Connection and Tunnel Security
      • KeeperPAM Licensing
      • Enforcement Policies
      • Vault Structure
      • Record Linking
      • Applications
      • Devices
      • Gateways
        • Creating a Gateway
        • Docker Installation
        • Linux Installation
        • Windows Installation
        • Auto Updater
        • Alerts and SIEM Integration
        • Advanced Configuration
          • Gateway Configuration with AWS KMS
          • Gateway Configuration with Custom Fields
      • PAM Configuration
        • AWS Environment Setup
        • Azure Environment Setup
        • Local Environment Setup
      • PAM Resources
        • PAM Machine
          • Example: Linux Machine
          • Example: Azure Windows VM
        • PAM Database
          • Example: MySQL Database
          • Example: PostgreSQL Database
          • Example: Microsoft SQL Server Database
        • PAM Directory
        • PAM Remote Browser
        • PAM User
      • Sharing and Access Control
      • Just-In-Time Access (JIT)
    • Password Rotation
      • Rotation Overview
      • Rotation Use Cases
        • Azure
          • Azure AD Users
          • Azure VM User Accounts
          • Azure Managed Database
            • Azure SQL
            • Azure MySQL - Single or Flexible Database
            • Azure MariaDB Database
            • Azure PostgreSQL - Single or Flexible Database
          • Azure App Secret Rotation
        • AWS
          • IAM User Password
          • Managed Microsoft AD User
          • EC2 Virtual Machine User
          • IAM User Access Key
          • Managed Database
            • AWS RDS for MySQL
            • AWS RDS for SQL Server
            • AWS RDS for PostgreSQL
            • AWS RDS for MariaDB
            • AWS RDS for Oracle
        • Local Network
          • Active Directory or OpenLDAP User
          • Windows User
          • Linux User
          • macOS User
          • Database
            • Native MySQL
            • Native MariaDB
            • Native PostgreSQL
            • Native MongoDB
            • Native MS SQL Server
            • Native Oracle
        • SaaS Accounts
          • Okta User
          • Snowflake User
          • Rotate Credential via REST API
        • Network Devices
          • Cisco IOS XE
          • Cisco Meraki
      • Service Management
      • Post-Rotation Scripts
        • Inputs and Outputs
        • Attaching Scripts
        • Code Examples
    • Connections
      • Getting Started
      • Session Protocols
        • SSH Connections
        • RDP Connections
        • MySQL Connections
        • SQL Server Connections
        • PostgreSQL Connections
        • VNC Connections
        • Telnet Connections
        • Kubernetes
        • RBI Connections
      • Examples
        • SSH Protocol - Linux Machine
        • RDP Protocol - Azure Virtual Machine
        • MySQL Protocol - MySQL Database
        • PostgreSQL Protocol - PostgreSQL Database
    • Tunnels
      • Setting up Tunnels
    • Remote Browser Isolation
      • Setting up RBI
        • URL Patterns & Resource URL Patterns
        • Browser Autofill
    • Session Recording & Playback
    • SSH Agent
      • Integration with Git
    • Discovery
      • Discovery Basics
      • Discovery using Commander
      • Discovery using the Vault
    • On-Prem Connection Manager
    • References
      • Port Mapping
      • Setting up SSH
      • Setting up WinRM
      • Setting up SQL Server
      • Database Import and Export
      • Installing sqlcmd on Linux
      • Installing Docker on Linux
      • Creating KSM App for Rotation
      • Active Directory Least Privilege
      • Event Reporting
      • Importing PAM Records
      • Managing Rotation via CLI
      • Commander SDK
      • Cron Spec
      • Preview Access
  • Endpoint Privilege Manager
    • Overview
    • Setup
    • Deployment
    • Policies
    • Managing Requests
  • FAQs
  • Secrets Manager
    • Secrets Manager Overview
    • Quick Start Guide
    • About KSM
      • Architecture
      • Terminology
      • Security & Encryption Model
      • One Time Access Token
      • Secrets Manager Configuration
      • Keeper Notation
      • Event Reporting
      • Field/Record Types
    • Secrets Manager CLI
      • Profile Command
      • Init Command
      • Secret Command
      • Folder Command
      • Sync Command
      • Exec Command
      • Config Command
      • Version Command
      • Misc Commands
      • Docker Container
      • Custom Record Types
    • Password Rotation
    • Developer SDKs
      • Python SDK
      • Java/Kotlin SDK
        • Record Field Classes
      • JavaScript SDK
      • .NET SDK
      • Go SDK
        • Record Field Classes
      • PowerShell
      • Vault SDKs
    • Integrations
      • Ansible
        • Ansible Plugin
        • Ansible Tower
      • AWS CLI Credential Process
      • AWS Secrets Manager Sync
      • AWS KMS Encryption
      • Azure DevOps Extension
      • Azure Key Vault Sync
      • Azure Key Vault Encryption
      • Bitbucket Plugin
      • Docker Image
      • Docker Runtime
      • Docker Writer Image
      • Entrust HSM Encryption
      • Git - Sign Commits with SSH
      • GitHub Actions
      • GitLab
      • Google Cloud Secret Manager Sync
      • Google Cloud Key Management Encryption
      • Hashicorp Vault
      • Heroku
      • Jenkins Plugin
      • Keeper Connection Manager
      • Kubernetes External Secrets Operator
      • Kubernetes (alternative)
      • Linux Keyring
      • Octopus Deploy
      • Oracle Key Vault Encryption
      • PowerShell Plugin
      • ServiceNow
      • TeamCity
      • Teller
      • Terraform Plugin
        • Terraform Registry
      • Windows Credential Manager
      • XSOAR
    • Troubleshooting
  • Commander CLI
    • Commander Overview
    • Installation and Setup
      • CLI Installation on Windows
      • CLI Installation on macOS
      • CLI Installation on Linux
      • Python Developer Setup
      • .NET Developer Setup
      • PowerShell Module
      • Logging in
      • Configuration and Usage
        • AWS Secrets Manager
        • AWS Key Management Service
      • Automating with Windows Task
      • Automating with AWS Lambda
      • Uninstallation
    • Command Reference
      • Import and Export Data
        • Import/Export Commands
        • CyberArk Import
        • LastPass Data Import
        • Delinea / Thycotic Secret Server Import
        • Keepass Import
        • ManageEngine Import
        • Myki Import
        • Proton Pass Import
        • CSV Import
        • JSON Import
      • Reporting Commands
        • Report Types
      • Enterprise Management Commands
        • Creating and Inviting Users
        • Compliance Commands
        • Breachwatch Commands
        • SCIM Push Configuration
      • Record Commands
        • Record Type Commands
        • Creating Record Types
      • Sharing Commands
      • KeeperPAM Commands
      • Connection Commands
        • SSH
        • SSH Agent
        • RDP
        • Connect Command
        • SFTP Sync
      • Secrets Manager Commands
      • MSP Management Commands
      • Miscellaneous Commands
      • Password Rotation
        • Password Rotation Commands
        • AWS Plugin
        • Azure Plugin
        • Microsoft SQL Server Plugin
        • MySQL Plugin
        • Oracle Plugin
        • PostgreSQL Plugin
        • PSPasswd Plugin
        • SSH Plugin
        • Unix Passwd Plugin
        • Windows Plugin
        • Active Directory Plugin
        • Automatic Execution
    • Service Mode REST API
    • Troubleshooting
Powered by GitBook

Company

  • Keeper Home
  • About Us
  • Careers
  • Security

Support

  • Help Center
  • Contact Sales
  • System Status
  • Terms of Use

Solutions

  • Enterprise Password Management
  • Business Password Management
  • Privileged Access Management
  • Public Sector

Pricing

  • Business and Enterprise
  • Personal and Family
  • Student
  • Military and Medical

© 2025 Keeper Security, Inc.

On this page
  • Overview
  • Prerequisites
  • Rotation Script Logic Flow
  • 1. Admin Credentials Retrieval
  • 2. Secret Rotation Logic
  • PAM User Record - Fields Requirements
  • Fields required:
  • Custom fields required:
  • Setting Up the Rotation in the Keeper Vault
  • Python Script

Was this helpful?

Export as PDF
  1. Privileged Access Manager
  2. Password Rotation
  3. Rotation Use Cases
  4. Azure

Azure App Secret Rotation

Automatically rotate the secret of an Azure app using Keeper Secrets Manager rotations

PreviousAzure PostgreSQL - Single or Flexible DatabaseNextAWS

Last updated 3 months ago

Was this helpful?

Overview

This documentation explains how to rotate Azure application secrets 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. The script ensures secure application secrets rotation, including deletion of previous application secrets, and stores the new application secret in Keeper. This new secret is automatically available to all already allowed KSM applications and users.

  • See the for a high level overview and getting started with Azure

Prerequisites

This guide assumes the following tasks have already taken place:

  • are configured for your role

  • A Keeper Secrets Manager has been created

  • Your Azure environment is per our documentation

  • The gateway host will need to have a supported Python version installed with the 2 dependencies below:

pip3 install keeper_secrets_manager_core
pip3 install azure.identity

Rotation Script Logic Flow

1. Admin Credentials Retrieval

The script retrieve admin credentials in three ways:

  1. Record directly attached to the post rotation script.

  2. The access key provided to the Azure PAM config selected for the rotation. This will be used if no access key is found in the record(s) attached (method 1 above) to the post rotation script.

Attaching another record containing the admin application secret to the PAM Script will allow to easily rotate this admin application secrets in that other record using the same process described in this documentation.

2. Secret Rotation Logic

The script will:

  1. Retrieve an admin application secret either from an attached record to the PAM Script or from the PAM Config.

  2. Get a Microsoft Graph access token using the admin application secret found at the step above.

  3. Create a new client secret on the Azure application defined in the PAM User record.

  4. Delete all other existing secrets for the defined Azure application. Only the one generated at the step above will be kept.

  5. Update the Keeper PAM User record with the new secret, and secret ID.

PAM User Record - Fields Requirements

Fields required:

Field Name
Description

Login

This mandatory field is not used in this script. You can use the field to store any useful information, like the name of the Azure app that will be rotated.

Password

It will be a dummy value in this case. The password field gets automatically rotated, but it is not used anywhere. This is still required field.

Custom fields required:

Custom Field Label
Field Type
Description

Text

This field is used to specify which application in Azure you want to rotate. You need to retrieve the application object ID of the application to rotate from the Azure portal > App Registration > Overview tab of your app > Application (client) ID.

Text

This field will receive the new client secret ID after the rotation.

Hidden Field

This field will receive the new client secret after the rotation.

Text

This field will receive the expiration date of the new secret after the rotation.

Text

Second field to enable NOOP.

The value has to be:

Instead of creating the PAM User record manually using the details above, you could also import the csv file below. It will create a template record you can amend and duplicate as needed.

Importing the file will generate a Login record type: make sure to convert it to PAM User.

Setting Up the Rotation in the Keeper Vault

The script require an admin application secret to authenticate against Azure and rotate another application's secret. Here we will be using the admin app secret provided in the Azure PAM Configuration.

Configuration From the Keeper Vault:

  1. Create a shared folder in the vault

  1. In the Secret Manager tab of the Keeper vault, create a new application for the gateway if there is no gateway yet.

  2. Make sure the Application has edit permissions on the shared folder created above.

  3. In the Secret Manager tab of the Keeper vault, go to the PAM Configurations tab. Create a new PAM configuration if needed.

  4. Under Environment, please select “Azure”, select the Gateway, select the shared folder, provide the “Entra ID” name (arbitrary name of your Entra ID environment), the admin application “Client ID” (Overview tab of the admin application in the Azure portal), “Client Secret” (Certificates & secrets tab of the admin application in the Azure portal), "Subscription ID" and "Tenant ID".

    • Password Rotation Settings: select your desired schedule and the PAM configuration created above.

    • Select "Run PAM Scripts only" as the Rotation method.

    • Add PAM Script to the record: select the provided file below and make sure to specify the script command:

python3

It is possible to also rotate the application secret of the admin Azure application. To do this, you will need to store you admin Azure app secret in another Keeper PAM User record.

Admin App Secret Record Requirements

Custom fields required:

Custom Field Label
Field Type
Description

Text

Enter your Azure Tenant ID.

Text

Enter your admin application client ID. This is available in the Overview tab of the admin application in the Azure portal > App registrations.

Importing the file will generate a Login record type: make sure to convert it to PAM User.

Configuration From the Keeper Vault:

  1. Create a shared folder in the vault

  1. In the Secret Manager tab of the Keeper vault, create a new application for the gateway if there is no gateway yet.

  2. Make sure the Application has edit permissions on the shared folder created above.

  3. In the Secret Manager tab of the Keeper vault, go to the PAM Configurations tab. Create a new PAM configuration if needed.

  4. Under Environment, please select “Local Network”, select the Gateway and the shared folder.

  1. Edit the target app PAM User record:

    • Password Rotation Settings: select your desired schedule and the PAM configuration created above.

    • Add PAM Script to the record:

      1. Select the provided Python file below.

      2. Rotation Credential: select the admin app PAM User record.

      3. Specify the script command:

python3

Python Script

import logging
import requests
import sys
import base64
import json
from azure.identity import ClientSecretCredential
from datetime import datetime, timedelta, timezone
from keeper_secrets_manager_core import SecretsManager
from keeper_secrets_manager_core.storage import FileKeyValueStorage

# sys.stdin is not an array, it cannot be subscripted (i.e., sys.stdin[0])
for base64_params in sys.stdin:
    # Retrieve params from the gateway
    params = json.loads(base64.b64decode(base64_params).decode('utf-8'))
    break

# Retrieve records attached to the post-rotation script in Keeper
records = json.loads(base64.b64decode(params.get('records')).decode('utf-8'))

# Initiate Keeper Secrets Manager SDK using the gateway configuration
secrets_manager = SecretsManager(config=FileKeyValueStorage('/etc/keeper-gateway/gateway-config.json'))

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

GRAPH_API_URL = "https://graph.microsoft.com/v1.0"

# Function to get Microsoft Graph access token using ClientSecretCredential
def get_access_token(tenant_id, client_id, client_secret):
    try:
        credential = ClientSecretCredential(tenant_id=tenant_id, client_id=client_id, client_secret=client_secret)
        token = credential.get_token("https://graph.microsoft.com/.default")
        logger.info("Authentication successful.")
        return token.token
    except Exception as e:
        logger.error(f"Authentication failed: {e}")
        raise

# Function to create a new client secret and return its ID and secret text
def create_client_secret(access_token, target_app_id, secret_expiry_days=365):
    try:
        # Set expiry time for the secret using timezone-aware datetime
        expiry_time = (datetime.now(timezone.utc) + timedelta(days=secret_expiry_days)).isoformat()

        # API request body to create a new client secret
        body = {
            "passwordCredential": {
                "displayName": "Client Secret Rotated by Keeper",
                "endDateTime": expiry_time
            }
        }

        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json"
        }

        response = requests.post(f"{GRAPH_API_URL}/applications/{target_app_id}/addPassword", json=body, headers=headers)
        
        if response.status_code == 200:
            response_json = response.json()
            new_client_secret = response_json['secretText']
            secret_id = response_json['keyId']  # Fetch the ID of the new secret

            # Log both the new secret and its ID
            logger.info(f"New client secret created for Application {target_app_id}. Secret ID: {secret_id}.")
            return secret_id, new_client_secret, expiry_time
        else:
            logger.error(f"Failed to create client secret: {response.status_code}, {response.text}")
            raise Exception(f"Failed to create client secret: {response.status_code}")
    except Exception as e:
        logger.error(f"An error occurred: {e}")
        raise

# Function to retrieve existing client secrets
def get_existing_client_secrets(access_token, target_app_id):
    try:
        headers = {
            "Authorization": f"Bearer {access_token}"
        }

        response = requests.get(f"{GRAPH_API_URL}/applications/{target_app_id}", headers=headers)
        if response.status_code == 200:
            app = response.json()
            logger.info(f"Retrieved existing client secrets for Application {target_app_id}.")
            return app.get('passwordCredentials', [])
        else:
            logger.error(f"Failed to retrieve client secrets: {response.status_code}, {response.text}")
            raise Exception(f"Failed to retrieve client secrets: {response.status_code}")
    except Exception as e:
        logger.error(f"An error occurred: {e}")
        raise

# Function to delete old client secrets, excluding the newly created one
def delete_old_secrets(access_token, target_app_id, new_client_secret_id):
    try:
        headers = {
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json"
        }

        existing_secrets = get_existing_client_secrets(access_token, target_app_id)
        
        # Iterate through all secrets and delete the ones that are not the newly created secret
        for secret in existing_secrets:
            if secret['keyId'] != new_client_secret_id:
                body = {
                    "keyId": secret['keyId']
                }
                response = requests.post(f"{GRAPH_API_URL}/applications/{target_app_id}/removePassword", json=body, headers=headers)

                if response.status_code == 204:
                    logger.info(f"Deleted secret with ID: {secret['keyId']} for Application {target_app_id}.")
                else:
                    logger.error(f"Failed to delete secret: {response.status_code}, {response.text}")
                    raise Exception(f"Failed to delete secret: {response.status_code}")
    except Exception as e:
        logger.error(f"An error occurred: {e}")
        raise

# Main function
def rotate_client_secret(tenant_id, client_id, client_secret, target_app_id, secret_expiry_days=365, delete_old_secrets_flag=False):
    try:
        # Step 1: Get access token
        access_token = get_access_token(tenant_id, client_id, client_secret)
        
        # Step 2: Create a new client secret and get its ID
        new_client_secret_id, new_client_secret, expiry_time = create_client_secret(access_token, target_app_id, secret_expiry_days)
        if not new_client_secret:
            logger.error("Failed to create a new client secret. Exiting.")
            return None, None, None  # Prevent further execution if secret creation failed
        
        logger.info(f"New client secret id: {new_client_secret_id}. Expiration date: {expiry_time}")
        
        # Step 3: Optionally delete old secrets, but exclude the newly created one
        if delete_old_secrets_flag:
            delete_old_secrets(access_token, target_app_id, new_client_secret_id)

        return new_client_secret_id, new_client_secret, expiry_time
    except Exception as e:
        logger.error(f"An error occurred during the secret rotation process: {e}")
        return None, None, None  # Return None in case of an error

# Inputs from the gateway and full PAM user query using KSM
pam_user_to_update = secrets_manager.get_secrets([params.get('userRecordUid')])[0]

# Retrieve app object id to rotate
try:
    target_app_id = pam_user_to_update.custom_field('application_object_id', single=True)
except Exception as e:
    raise RuntimeError(f"No app object id found, the script will exit after this error: {e}")

# Retrieve admin user account access key
admin_cred_provided = False
for record in records:
    if "application_client_id" in record:
        client_id = record["application_client_id"]
        client_secret = record["client_secret"]
        tenant_id = record["tenant_id"]
        admin_cred_provided = True
        logger.info(f"Admin creds provided. Using secret_id {client_id}, from attached record UID {record['uid']}.")
        break
    elif "clientId" in record:
        client_id = record["clientId"]
        client_secret = record["clientSecret"]
        tenant_id = record["tenantId"]
        admin_cred_provided = True
        logger.info(f"Admin creds provided. Using secret_id {client_id}, from attached PAM Config record UID {record['uid']}.")
        break

# If there is no app key provided to an attached record, then use the PAM config.
if admin_cred_provided == False:
    try:
        pam_config = secrets_manager.get_secrets([params.get('providerRecordUid')])[0]
        client_id = pam_config.field('clientId', single=True)
        client_secret = pam_config.field('clientSecret', single=True)
        tenant_id = pam_config.field('tenantId', single=True)
        admin_cred_provided = True
        logger.info(f"Using PAM config creds. Using secret_id {client_id}, from PAM Config UID.")
    except Exception as e:
        logger.error(f"Error retrieving admin creds from PAM config: {e}")

# Exit if no credentials were provided
if not admin_cred_provided:
    raise RuntimeError("No admin creds provided. The script will exit.")

# Rotate client secret and handle success/failure
new_client_secret_id, new_client_secret, expiry_time = rotate_client_secret(
    tenant_id, client_id, client_secret, target_app_id, secret_expiry_days=365, delete_old_secrets_flag=True
)

if new_client_secret_id and new_client_secret:
    logger.info("\nRotation Result:")
    logger.info(f"New secret id: {new_client_secret_id}")

    # Update the PAM User record in Keeper
    try:
        pam_user_to_update.custom_field('client_secret_id', new_client_secret_id)
        pam_user_to_update.custom_field('client_secret', new_client_secret)
        pam_user_to_update.custom_field('expires', expiry_time)
        secrets_manager.save(pam_user_to_update)
    except Exception as e:
        logger.error(f"Error while updating PAM User record in Keeper: {e}")

You need to create a record where the rotation will be configured later on. The fields below need to be created.

Create a PAM User record in the shared folder with the fields and custom fields described .

Provision the gateway (gateway tab after selecting the application) on a Linux box. Simply run the install command provided by the Keeper vault and make sure Python and the dependencies listed are installed.

Edit the PAM User record previously described in this :

The PAM user record will need all fields as described in the documentation , along with the additional fields below:

Instead of creating the PAM User record manually using the documentation and the extra fields above, you could also import the csv file below. It will create a template record you can amend and duplicate as needed.

Create a PAM User record in the shared folder with the fields and custom fields described .

Provision the gateway (gateway tab after selecting the application) on a Linux box. Simply run the install command provided by the Keeper vault and make sure Python and the dependencies listed are installed.

application_object_id
client_secret_id
client_secret
expires
Private Key Type
rsa-ssh
tenant_id
application_client_id
Azure Overview
Rotation enforcements
application
configured
PAM User
above
above
above
above
documentation
above
above
237B
PAM User Template AzureAppSecretRotation.csv
CSV file to import in the Keeper vault
338B
PAM User Template AdminAzureAppSecretRotation.csv
CSV file to import in the Keeper vault
9KB
RotateAzureApp.py
PAM User record example
PAM User record in shared folder
Azure PAM Config
Attach PAM Script to the PAM User record
Shared folder with two records: admin app and target app
Local Network PAM Config
Attach PAM Script with Rotation Credential