INC-2026-02-14-001: ISE SAML SSO Restoration

Incident Summary

Incident ID

INC-2026-02-14-001

Severity

Medium (Admin access impacted)

Detection

2026-02-14 ~18:00

Resolution

2026-02-14 ~19:30

Duration

~90 minutes

Status

Resolved

Executive Summary

Successfully restored ISE Admin Portal SAML SSO authentication with Keycloak IdP after restoring ise-02 backup to ise-01. All configuration changes performed via Keycloak REST API - no GUI interaction required.

Timeline

Time Event

~16:00

Restored ise-02 backup to ise-01 (ISE 3.4 → ISE 3.4)

~17:30

Attempted SAML login - redirect loop detected

~18:00

Identified root cause: Keycloak SAML client redirect URIs point to ise-02

~18:15

Downloaded ISE SP metadata (keycloak_01.zip)

~18:30

Extracted Entity ID: CiscoISE/a486c6ef-6c77-4bc1-bf6d-4e479b3aeae8

~18:45

Verified Keycloak client exists with matching Entity ID

~19:00

Updated Keycloak client via REST API (ise-02 → ise-01)

~19:15

Tested SAML login - SUCCESS

~19:30

Documented resolution

Impact

  • ISE Admin Portal SAML SSO unavailable for ~90 minutes

  • Local admin account (admin) remained available as fallback

  • No impact to RADIUS/802.1X authentication

  • No impact to pxGrid, ERS, or OpenAPI services

Root Cause

When restoring an ISE backup to a different hostname:

  1. ISE SAML Service Provider (SP) configuration remains intact

  2. ISE Entity ID is per-deployment (UUID-based), not hostname-based

  3. Keycloak SAML client had redirect URIs hardcoded to ise-02

  4. SAML assertion destination URL mismatched new hostname

Entity ID Structure

ISE generates a unique Entity ID per deployment:

http://CiscoISE/{deployment-uuid}

This Entity ID survived the backup/restore because it’s stored in the ISE database.

Resolution

Approach: Keycloak REST API

Used Keycloak Admin REST API for: * Reproducibility - commands can be scripted * Documentation - exact steps captured * Skill building - API-first approach for automation

CLI Mastery: Keycloak REST API

Step 1: Retrieve Admin Token

KC_ADMIN_PASS="<password-from-dsec>"

KC_TOKEN=$(curl -s -X POST \
  "https://keycloak-01.inside.domusdigitalis.dev:8443/realms/master/protocol/openid-connect/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "username=admin" \
  -d "password=$KC_ADMIN_PASS" \
  -d "grant_type=password" \
  -d "client_id=admin-cli" \
  --insecure | jq -r '.access_token')

Step 2: List SAML Clients

curl -s "https://keycloak-01.inside.domusdigitalis.dev:8443/admin/realms/domusdigitalis/clients" \
  -H "Authorization: Bearer $KC_TOKEN" \
  --insecure | jq '.[] | select(.protocol=="saml") | {id, clientId, name}'

Output:

{
  "id": "0d7b3b6b-d32f-49a0-9563-6cc8e645b59c",
  "clientId": "http://CiscoISE/a486c6ef-6c77-4bc1-bf6d-4e479b3aeae8",
  "name": "Cisco ISE Admin Portal (ise-02)"
}

Step 3: Export Full Configuration

CLIENT_UUID="0d7b3b6b-d32f-49a0-9563-6cc8e645b59c"

curl -s "https://keycloak-01.inside.domusdigitalis.dev:8443/admin/realms/domusdigitalis/clients/$CLIENT_UUID" \
  -H "Authorization: Bearer $KC_TOKEN" \
  --insecure > /tmp/ise-saml-client.json

Step 4: Transform with sed

sed 's/ise-02/ise-01/g' /tmp/ise-saml-client.json > /tmp/ise-saml-client-updated.json

Changes applied:

Field New Value

name

Cisco ISE Admin Portal (ise-01)

rootUrl

ise-01.inside.domusdigitalis.dev

adminUrl

ise-01.inside.domusdigitalis.dev

redirectUris

ise-01.inside.domusdigitalis.dev:8443/*

webOrigins

ise-01.inside.domusdigitalis.dev

saml_assertion_consumer_url_post

ise-01.inside.domusdigitalis.dev:8443/portal/SSOLoginResponse.action

Step 5: Apply Update via PUT

curl -s -X PUT \
  "https://keycloak-01.inside.domusdigitalis.dev:8443/admin/realms/domusdigitalis/clients/$CLIENT_UUID" \
  -H "Authorization: Bearer $KC_TOKEN" \
  -H "Content-Type: application/json" \
  -d @/tmp/ise-saml-client-updated.json \
  --insecure -w "\nHTTP_STATUS: %{http_code}\n"

Result: HTTP_STATUS: 204 (No Content = success)

Step 6: Verify

curl -s "https://keycloak-01.inside.domusdigitalis.dev:8443/admin/realms/domusdigitalis/clients/$CLIENT_UUID" \
  -H "Authorization: Bearer $KC_TOKEN" \
  --insecure | jq '{name, rootUrl, redirectUris}'

Output:

{
  "name": "Cisco ISE Admin Portal (ise-01)",
  "rootUrl": "https://ise-01.inside.domusdigitalis.dev",
  "redirectUris": [
    "https://ise-01.inside.domusdigitalis.dev:8443/*",
    "https://ise-01.inside.domusdigitalis.dev/*"
  ]
}

Step 7: Test SAML Login

  1. Navigate to ise-01.inside.domusdigitalis.dev/admin/

  2. Click "Log in with SAML"

  3. Redirect to Keycloak login

  4. Authenticate as evanusmodestus

  5. Redirect to ISE Admin Portal - SUCCESS

Key Lessons

Lesson Action

ISE SAML is GUI-only

No ERS/OpenAPI endpoint exists for SAML IdP configuration

Keycloak is fully API-driven

All SAML client updates can be automated via REST API

Entity ID survives restores

ISE Entity ID is deployment-specific, not hostname-specific

sed is powerful

Simple sed 's/old/new/g' transformed all hostname references

PUT requires full object

Keycloak doesn’t support PATCH - GET/modify/PUT workflow required

Prevention Checklist

Pre-Restore

  • Document current ISE Entity ID from SP metadata

  • Export Keycloak SAML client configuration

  • Note all hostname-specific URLs

Post-Restore

  • Verify ISE Entity ID unchanged

  • Update Keycloak SAML client redirect URIs

  • Update ACS URL in client attributes

  • Test SAML login before declaring restore complete

Automation Opportunity

Create netapi keycloak update-saml-client command:

netapi keycloak update-saml-client \
  --realm domusdigitalis \
  --client-id "http://CiscoISE/a486c6ef-6c77-4bc1-bf6d-4e479b3aeae8" \
  --old-hostname ise-02 \
  --new-hostname ise-01

Metadata

Field Value

Incident ID

INC-2026-02-14-001

Author

Evan Rosado

Date Created

2026-02-14

Status

Resolved

Category

Identity / SAML SSO