Navigation
GuidesUpdated July 3, 2026

Azure Service Principal Rotation

guideazurespnservice-principalsecuritysecret-rotationvaultterraformazure-cli

How to Rotate Azure Service Principal Names (SPNs)

Requirements

Known SPNs

  • service-principals/ohemr-spn-citrix-test_west-001
  • service-principals/ohemr-spn-citrix-npd_west-001

Secrets for these SPNs are set to expire on 05/26/202!!!!

Owners:

  • Palacios, Manuel
  • Hudak, Thomas
  • O'Shea, Patrick
  • Shahsavand, Amir

Rotate SPN Secrets - Process

1. #Identify the SPNs

ohemr-spn-citrix-npd_west-001
ohemr-spn-citrix-test_west-001

2. Log onto Hashicorp Vault with your primary MSID

vault login -method=ldap -address=https://vault.uhg.com username=mpalac17

3. Validate that you can read the SPNs from Hashicorp Vault

vault kv get -address=https://vault.uhg.com --namespace="aide-0085665" kv/service-principals/ohemr-spn-citrix-npd_west-001 vault kv get -address=https://vault.uhg.com --namespace="aide-0085665" kv/service-principals/ohemr-spn-citrix-test_west-001

4. Log onto Azure Portal. Make sure to select your OptumCloud ID when prompted

az login

  • If you do not have az installed, you can install it via brew: brew install azure-cli
  • If you receive any SSL Certificate Verify Failed messages after running the command, you can export REQUESTS_CA_BUNDLE and set it to a path to any standard Optum CA on your device export REQUESTS_CA_BUNDLE=/my/path/to/optum/standard_trusts.pem
  • CA Bundle can be downloaded from artifactory: standard_trusts
  • After logging in you may be prompted to choose a subscription. SPNs are subscription agnostic so you may choose any one available (e.g. OHEMR-SUB-EPIC-SHARED-001)

5. Validate that you can read the SPNs from Azure. Use the ‘client_id’ values from step 3

az ad app credential list --id de3cba1e-836f-4849-b012-e9310f911851 az ad app credential list --id eeef1eda-462f-4b29-adad-f4edc9819d11

6. Locate script rotate_spn_secret.sh, ensure that the SPNs and IDs at the top of the file match the ones you have identified

cat ~/rotate_spn_secret.sh | grep -E "^spn_(names|ids)="

7. Execute script rotate_spn_secrets.sh

~/rotate_spn_secret.sh

8. Validate that secrets have rotated

vault kv get --namespace="aide-0085665" kv/service-principals/ohemr-spn-citrix-npd_west-001
az ad app credential list --id de3cba1e-836f-4849-b012-e9310f911851

vault kv get --namespace="aide-0085665" kv/service-principals/ohemr-spn-citrix-test_west-001
az ad app credential list --id eeef1eda-462f-4b29-adad-f4edc9819d11

Rotate SPN Secrets - rotate_spn_secret.sh

#!/bin/bash

spn_names=("ohemr-spn-citrix-npd_west-001" "ohemr-spn-citrix-test_west-001")
spn_ids=("de3cba1e-836f-4849-b012-e9310f911851" "eeef1eda-462f-4b29-adad-f4edc9819d11")

export VAULT_NAMESPACE="aide-0085665"

# Function to get service principal details
get_spn_details() {
local spn_id=$1
az ad sp show --id "$spn_id" --query "{client_id: appId}" -o json
}

# Function to rotate service principal secret (password)
rotate_spn_secret() {
local spn_id=$1
local spn_name=$2
local secret_name=${spn_name/spn/secret}
local end_date=$(date -v+90d "+%Y-%m-%d")
az ad app credential reset --id "$spn_id" --display-name "$secret_name" --end-date "$end_date" --query "{client_secret: password}" -o json
}

# Loop through each service principal name
for i in "${!spn_names[@]}"; do
spn_name="${spn_names[$i]}"
spn_id_value="${spn_ids[$i]}"
# Get the service principal details
spn_details=$(get_spn_details "$spn_id_value")
client_id=$(echo $spn_details | jq -r '.client_id')

# Check if details were retrieved successfully
if [[ -z "$client_id" ]]; then
    echo "Failed to retrieve details for $spn_name"
    continue
fi

# Output the Vault command that will be run
echo "vault kv put -namespace=$VAULT_NAMESPACE kv/service-principals/$spn_name client_id=\"$client_id\" client_secret=\"<to be fetched>\""

# Rotate the service principal secret
spn_secret=$(rotate_spn_secret "$spn_id_value" "$spn_name")
if [[ $? -ne 0 ]]; then
    echo "Failed to rotate secret for $spn_name. Ensure you have sufficient privileges."
    continue
fi

client_secret=$(echo $spn_secret | jq -r '.client_secret')

# Check if secret was retrieved successfully
if [[ -z "$client_secret" ]]; then
    echo "Failed to retrieve secret for $spn_name"
    continue
fi

# Output the full Vault command with the actual secret
echo "vault kv put -namespace=$VAULT_NAMESPACE kv/service-principals/$spn_name client_id=\"$client_id\" client_secret=\"$client_secret\""

# Store the credentials in Vault
vault kv put -namespace=$VAULT_NAMESPACE kv/service-principals/$spn_name client_id="$client_id" client_secret="$client_secret"
echo "Stored credentials for $spn_name in Vault."
done