ERS Authorization Profiles
Authorization profiles define what happens after authentication succeeds. VLAN assignment, dACL enforcement, SGT tagging - this is where policy meets enforcement.
Quick Reference
| Command | Purpose |
|---|---|
|
List all profiles |
|
Get single profile details |
|
JSON output for jq piping |
|
Change VLAN assignment |
|
Change dACL assignment |
|
Remove dACL from profile |
Profile Anatomy
An authorization profile contains:
{
"id": "298c2780-f164-11f0-b76e-52c54a1d1f56",
"name": "Research_Onboard",
"description": "MAB onboarding for research endpoints",
"accessType": "ACCESS_ACCEPT", // or ACCESS_REJECT
"authzProfileType": "SWITCH", // SWITCH, WLAN, or TRUSTSEC
"vlan": {
"nameID": "IOT_VLAN", // Must match switch VLAN name!
"tagID": 1 // RADIUS attribute tag
},
"daclName": "RESEARCH_ONBOARD_DACL", // Downloadable ACL
"profileName": "Cisco", // Network device profile
"reauth": {
"timer": 3600, // Reauth interval (seconds)
"connectivity": "RADIUS_REQUEST" // or DEFAULT, MAINTAIN_CONNECTIVITY
}
}
The vlan.nameID must be the exact switch VLAN name (e.g., IOT_VLAN), not a numeric ID (e.g., 40). Numeric IDs cause silent failures - the switch won’t apply the VLAN even though ISE reports success.
|
JQ-FU: Advanced Query Patterns
|
The
|
Color Code Reference
| Color | ANSI Code | jq Syntax |
|---|---|---|
Green |
|
|
Yellow |
|
|
Blue |
|
|
Cyan |
|
|
Bold White |
|
|
Red |
|
|
Reset |
|
|
Pattern 1: One-Liner Status Card
Quick glance at a profile’s key settings:
# One-liner status card (colored)
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/${PROFILE_NAME}" | \
jq -r '.AuthorizationProfile | "\u001b[32m[\(.accessType)]\u001b[0m \u001b[1;37m\(.name)\u001b[0m → VLAN:\u001b[33m\(.vlan.nameID // "none")\u001b[0m dACL:\u001b[36m\(.daclName // "none")\u001b[0m"'
[ACCESS_ACCEPT] Research_Onboard → VLAN:IOT_VLAN dACL:RESEARCH_ONBOARD_DACL
Pattern 2: Beautiful Box Summary
Full profile details with box-drawing characters:
# Beautiful profile summary with all fields
PROFILE_NAME="Research_Onboard"
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/${PROFILE_NAME}" | \
jq -r '.AuthorizationProfile | "
\u001b[1;37m╔══════════════════════════════════════════════════════════════╗
║ AUTHORIZATION PROFILE: \(.name | .[0:38])\u001b[0m\u001b[1;37m
╠══════════════════════════════════════════════════════════════╣\u001b[0m
\u001b[34mID:\u001b[0m \(.id)
\u001b[34mAccess:\u001b[0m \u001b[32m\(.accessType)\u001b[0m
\u001b[34mType:\u001b[0m \(.authzProfileType)
\u001b[34mVLAN:\u001b[0m \u001b[33m\(.vlan.nameID // "none")\u001b[0m (tag: \(.vlan.tagID // "-"))
\u001b[34mdACL:\u001b[0m \u001b[36m\(.daclName // "none")\u001b[0m
\u001b[34mProfile:\u001b[0m \(.profileName // "-")
\u001b[34mDesc:\u001b[0m \(.description // "-")
\u001b[1;37m╚══════════════════════════════════════════════════════════════╝\u001b[0m"'
╔══════════════════════════════════════════════════════════════╗ ║ AUTHORIZATION PROFILE: Research_Onboard ╠══════════════════════════════════════════════════════════════╣ ID: 298c2780-f164-11f0-b76e-52c54a1d1f56 Access: ACCESS_ACCEPT Type: SWITCH VLAN: IOT_VLAN (tag: 1) dACL: RESEARCH_ONBOARD_DACL Profile: Cisco Desc: MAB onboarding for research endpoints ╚══════════════════════════════════════════════════════════════╝
Pattern 3: ALL Profiles Table
Audit every profile’s VLAN and dACL assignment:
# ALL profiles table with VLAN + dACL (colored, production-ready)
printf "\u001b[1;37m%-35s %-15s %-30s\u001b[0m\n" "PROFILE" "VLAN" "DACL"
printf "%s\n" "$(printf '=%.0s' {1..80})"
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile" | \
jq -r '.SearchResult.resources[].name' | sort | while read name; do
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/$name" 2>/dev/null | \
jq -r --arg n "$name" '.AuthorizationProfile |
"\u001b[1;37m\($n)\u001b[0m\t\u001b[33m\(.vlan.nameID // "-")\u001b[0m\t\u001b[36m\(.daclName // "-")\u001b[0m"'
done | column -t -s $'\t'
PROFILE VLAN DACL ================================================================================ Blackhole_Wireless_Access - BLACKHOLE_DENY Cisco_IP_Phones VOICE_VLAN PERMIT_ALL Deny_Access - - Linux_EAPTLS_Permit DATA_VLAN Linux-Permit-AD-Only Research_EAPTLS_Permit IOT_VLAN RESEARCH_PERMIT_DACL Research_Onboard IOT_VLAN RESEARCH_ONBOARD_DACL
Pattern 4: Stale VLAN Finder
Find profiles using numeric VLAN IDs (misconfiguration):
# Find profiles with stale/numeric VLAN IDs (potential misconfig)
echo -e "\u001b[1;37mProfiles with numeric VLAN IDs (potential misconfiguration):\u001b[0m"
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile" | \
jq -r '.SearchResult.resources[].name' | while read name; do
RESULT=$(curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/$name" 2>/dev/null | \
jq -r '.AuthorizationProfile | select(.vlan.nameID != null) |
select(.vlan.nameID | test("^[0-9]+$")) |
"\(.name)\t\(.vlan.nameID)"')
if [ -n "$RESULT" ]; then
echo -e " \u001b[31m⚠\u001b[0m $RESULT" | column -t -s $'\t'
fi
done
Profiles with numeric VLAN IDs (potential misconfiguration): ⚠ Legacy_Profile 40 ⚠ Old_Guest 999
Pattern 5: dACL Audit
Group all profiles by their dACL assignment:
# dACL audit: Group profiles by dACL assignment
echo -e "\u001b[1;37mProfiles grouped by dACL:\u001b[0m"
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile" | \
jq -r '.SearchResult.resources[].name' | while read name; do
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/$name" 2>/dev/null | \
jq -r --arg n "$name" '[(.AuthorizationProfile.daclName // "NO_DACL"), $n] | @tsv'
done | sort | awk -F'\t' '
BEGIN { prev="" }
{
if ($1 != prev) {
printf "\n\033[36m%s\033[0m\n", $1
prev = $1
}
printf " \033[33m%s\033[0m\n", $2
}'
BLACKHOLE_DENY Blackhole_Wireless_Access Linux-Permit-AD-Only Linux_EAPTLS_Permit NO_DACL Deny_Access PERMIT_ALL Cisco_IP_Phones RESEARCH_ONBOARD_DACL Research_Onboard
Pattern 6: Profile Diff
Compare two profiles side-by-side:
# Compare two profiles side-by-side (diff)
PROFILE_A="Research_Onboard"
PROFILE_B="Research_EAPTLS_Permit"
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/${PROFILE_A}" | \
jq '.AuthorizationProfile | del(.id, .link)' > /tmp/prof_a.json
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/${PROFILE_B}" | \
jq '.AuthorizationProfile | del(.id, .link)' > /tmp/prof_b.json
diff --color=always <(jq -S . /tmp/prof_a.json) <(jq -S . /tmp/prof_b.json)
2c2 < "name": "Research_Onboard", --- > "name": "Research_EAPTLS_Permit", 4c4 < "description": "MAB onboarding for research endpoints - limited access", --- > "description": "EAP-TLS authenticated research endpoints - full access", 8c8 < "daclName": "RESEARCH_ONBOARD_DACL", --- > "daclName": "RESEARCH_PERMIT_DACL",
Pattern 7: VLAN Audit
Find all profiles using a specific VLAN:
# VLAN audit: Find profiles using specific VLAN (or missing VLAN)
TARGET_VLAN="IOT_VLAN" # or "none" to find profiles without VLAN
echo -e "\u001b[1;37mProfiles using VLAN: ${TARGET_VLAN}\u001b[0m"
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile" | \
jq -r '.SearchResult.resources[].name' | while read name; do
VLAN=$(curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/$name" 2>/dev/null | \
jq -r '.AuthorizationProfile.vlan.nameID // "none"')
if [ "$VLAN" = "$TARGET_VLAN" ]; then
echo -e " \u001b[32m✓\u001b[0m $name"
fi
done
Pattern 8: Filter Non-Empty Values
Strip out all the false/null/empty noise:
# Only non-null, non-false, non-empty values (the interesting stuff)
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/${PROFILE_NAME}" | \
jq '.AuthorizationProfile | with_entries(select(.value != false and .value != null and .value != ""))'
Pattern 9: Flatten to Key-Value
Grep-friendly vertical output:
# Vertical key=value (grep-friendly, colored)
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/${PROFILE_NAME}" | \
jq -r '.AuthorizationProfile | to_entries[] | "\u001b[34m\(.key)\u001b[0m=\u001b[33m\(.value)\u001b[0m"'
Pattern 10: netapi + jq + awk Pipeline
Full pipeline with colored awk output:
# netapi with global --format option (jq-friendly)
netapi ise --format json get-authz-profile "Research_Onboard" | jq '.'
# Extract specific fields
netapi ise --format json get-authz-profile "Research_Onboard" | jq '{name, vlan: .vlan.nameID, dacl: .daclName}'
# Pipe to awk for custom formatting
netapi ise --format json get-authz-profile "Research_Onboard" | \
jq -r '[.name, .vlan.nameID, .daclName] | @tsv' | \
awk -F'\t' '{printf "\033[1;37m%s\033[0m VLAN:\033[33m%s\033[0m dACL:\033[36m%s\033[0m\n", $1, $2, $3}'
Troubleshooting
VLAN_NOT_FOUND Error
AUTHMGR-5-FAIL: Authorization failed or unapplied for client
VLAN_NOT_FOUND: RESEARCH_VLAN
Root Cause: Profile has stale VLAN name that doesn’t exist on switch.
Fix:
# Check current VLAN assignment
netapi ise --format json get-authz-profile "Research_Onboard" | jq '.vlan'
# Update to correct VLAN name
netapi ise update-authz-profile "Research_Onboard" --vlan IOT_VLAN
# Verify
netapi ise --format json get-authz-profile "Research_Onboard" | jq '.vlan.nameID'
Numeric VLAN ID Issue
ISE shows "nameID": "40" instead of "nameID": "IOT_VLAN".
Problem: Using numeric ID causes silent authorization failures.
Fix:
netapi ise update-authz-profile "Research_Onboard" --vlan IOT_VLAN
Find All Misconfigured Profiles
# One-liner to find numeric VLANs
curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile" | \
jq -r '.SearchResult.resources[].name' | while read name; do
VLAN=$(curl -ks -H "Authorization: Basic $ISE_API_TOKEN" -H "Accept: application/json" \
"https://${ISE_HOST}:9060/ers/config/authorizationprofile/name/$name" 2>/dev/null | \
jq -r '.AuthorizationProfile.vlan.nameID // empty')
if [[ "$VLAN" =~ ^[0-9]+$ ]]; then
echo -e "\033[31m⚠ NUMERIC:\033[0m $name → $VLAN"
fi
done
CRUD Operations
Create Profile
# Basic with VLAN
netapi ise create-authz-profile "New_Profile" --vlan DATA_VLAN
# With VLAN + dACL
netapi ise create-authz-profile "Secure_Profile" --vlan DATA_VLAN --dacl PERMIT_AD_ONLY
# With description and reauth
netapi ise create-authz-profile "IoT_Profile" \
--vlan IOT_VLAN \
--dacl IOT_RESTRICTED \
--descr "IoT devices - restricted access" \
--reauth-timer 3600
Update Profile
# Change VLAN
netapi ise update-authz-profile "Research_Onboard" --vlan IOT_VLAN
# Change dACL
netapi ise update-authz-profile "Research_Onboard" --dacl NEW_DACL
# Remove dACL
netapi ise update-authz-profile "Research_Onboard" --no-dacl
# Set reauth timer (8 hours)
netapi ise update-authz-profile "Research_Onboard" --reauth-timer 28800
Bulk Operations from YAML
netapi ise create-authz-profiles-from-file profiles.yaml
netapi ise update-authz-profiles-from-file profiles.yaml
profiles:
- name: Domus_Secure_Profile
description: "Trusted users - full network access"
access_type: ACCESS_ACCEPT
vlan:
name: DATA_VLAN
tag: 1
dacl_name: DACL_SECURE_FULL
reauth_timer: 28800
- name: Domus_IoT_Profile
description: "IoT devices - restricted access"
access_type: ACCESS_ACCEPT
vlan:
name: IOT_VLAN
tag: 1
dacl_name: DACL_IOT_RESTRICTED
reauth_timer: 3600
Raw curl Examples
For automation or when netapi isn’t available:
Get Profile
# Get profile by name
PROFILE_NAME="Linux-EAP-TLS-Access"
curl -sk -u "${ISE_AUTH}" \
"${BASE_URL}/authorizationprofile/name/${PROFILE_NAME}" \
-H "Accept: application/json" | jq '.AuthorizationProfile'
Create Profile with dACL
# Create profile with dACL
# First get dACL ID
DACL_NAME="Linux-Permit-AD-Only"
DACL_ID=$(curl -sk -u "${ISE_AUTH}" \
"${BASE_URL}/downloadableacl/name/${DACL_NAME}" \
-H "Accept: application/json" | jq -r '.DownloadableAcl.id')
curl -sk -u "${ISE_AUTH}" \
"${BASE_URL}/authorizationprofile" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-X POST \
-d '{
"AuthorizationProfile": {
"name": "Linux-EAP-TLS-Access",
"description": "EAP-TLS authenticated Linux with AD-only dACL",
"accessType": "ACCESS_ACCEPT",
"authzProfileType": "SWITCH",
"daclName": "'"${DACL_NAME}"'",
"vlan": {
"nameID": "DATA",
"tagID": 10
}
}
}'
Clone Profile
# Clone existing profile with new name
SOURCE_PROFILE="Linux-EAP-TLS-Access"
NEW_PROFILE="Linux-EAP-TLS-Access-v2"
# Get source profile
PROFILE=$(curl -sk -u "${ISE_AUTH}" \
"${BASE_URL}/authorizationprofile/name/${SOURCE_PROFILE}" \
-H "Accept: application/json")
# Remove ID and update name
NEW_PROFILE_JSON=$(echo "$PROFILE" | jq '.AuthorizationProfile | del(.id) | del(.link) | .name = "'"${NEW_PROFILE}"'" | .description = "Cloned from '"${SOURCE_PROFILE}"'"')
curl -sk -u "${ISE_AUTH}" \
"${BASE_URL}/authorizationprofile" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-X POST \
-d "{\"AuthorizationProfile\": ${NEW_PROFILE_JSON}}"
Export All Profiles
# Export all profiles to JSON
curl -sk -u "${ISE_AUTH}" \
"${BASE_URL}/authorizationprofile" \
-H "Accept: application/json" | \
jq -r '.SearchResult.resources[].id' | while read ID; do
curl -sk -u "${ISE_AUTH}" \
"${BASE_URL}/authorizationprofile/${ID}" \
-H "Accept: application/json"
done | jq -s '.' > authz-profiles-backup.json